slides: clickable web links, * and indented bullets, and a complete shortcut list - #465
Merged
Merged
Conversation
…hortcut list Three things from the issue and discussion scan, each small enough for this release. Links (#421; discussions #373, #374). An element's `link` may be an http(s) URL as well as a slide id (Presenting → Web link), and [caption](https://…) in a text box becomes an <a href>. One scheme test, isWebUrl in model.ts, is asked by every surface: the shape gate, the text sanitizer (an <a> keeps its href only when it passes and is unwrapped to text otherwise), both markdown converters, and the show before it opens anything — always a NEW tab with noopener,noreferrer, never a navigation of the deck, target/rel decided at click time and never stored. In the editor a link click edits text. The offline switch is honoured with a toast. Measured in Chrome: text and element links open via window.open with those flags and the deck stays; a javascript: anchor is unwrapped; slide links still jump; the editor never navigates; with offline mode on nothing opens. test-sanitize gains the anchor checks (143 in the browser); scripts/test-slides-links.ts (27) pins isWebUrl, the converters, and by shape the four callers. Bullets (#255, #368). "* " makes a bullet like "- ", and two or more leading spaces make an indented sub-bullet — while typing (autoformat, undoable with ⌘Z like before) and when pasting markdown (indent kept as NBSPs). Shortcuts (#269). The ? overlay names B, G, [ ], ⌘B/I/U, the zoom keys, and the arrow keys' two jobs. Sixteen strings in all eight catalogs and all 22 packs (four of them pre-existing gaps from the code-snippet work); changelog entries; DECISIONS entry for the link decisions.
auxclick is not click: the browser's default for it on an anchor is 'open in a new tab', straight past the offline gate and the noreferrer flag. Intercepted on the show's slide host; button 1 opens through openWeb like a click, anything else is swallowed. Pinned by shape.
Build size
Updated: |
The walk visited a snapshot of a node's children; an unknown element's children were lifted into place after the snapshot and not visited on that pass. Walk first, then lift, so lifted children are held to the same rule as their siblings. One case in test-sanitize: nested markup is walked like top-level markup, rendered and exercised in the browser.
…are walked before it is unwrapped rel='noopener noreferrer' is set on the show's anchors when a section is built — never stored, never in render.ts — so the browser's own routes to an anchor (context menu, drag), which do not pass through the click handler, send no referrer on a hosted deck either. The refused-anchor unwrap now walks its contents first, like the general unwrap. The nested-markup sanitizer case gains the anchor shape.
…es on rebase once #467 lands)
Owner
Author
|
Residual worth stating: the browser's own routes to a visible link — the context menu's open in new tab, dragging the link out — do not pass through the show's click handler, so they bypass the offline gate; they are the viewer's own act on a visible link and are not intercepted. Show anchors now carry |
…temporary merge of the fix branch is now redundant
#466 landed an isWebUrl beside the embed element (scheme only); this branch had a stricter one (scheme, length, no quote or angle bracket). One definition now, the stricter, where the embed's was; every caller (embed url gate, live-frame gate, element link, text anchor, markdown) asks it. The embed rig passes against it unchanged (64/64).
…s before this branch's
…s before this branch's
nyblnet
added a commit
that referenced
this pull request
Sep 14, 2026
…n the features, once — thirteen entries
nyblnet
added a commit
that referenced
this pull request
Sep 15, 2026
…, formulas matched in text segments only (an href with $ stays intact), formula cache capped at 256 resolveMath split on tags and transformed only the text runs: since #465 a link whose href carried $a$b had a <math> written into the attribute (dead link, stray markup — nothing author-chosen became an attribute). The rig now asserts every style value the emitter produces is one of its constant forms or a bare colour, that a quote in \text reaches the output as ", the three href cases, and the LRU cap.
lasjoh-toho
added a commit
to lasjoh-toho/bento
that referenced
this pull request
Sep 15, 2026
…ream, embed element deliberately excluded) Adopted the links + bullets half of upstream nyblnet/bento's own nyblnet#465 ("clickable web links, * and indented bullets, and a complete shortcut list"). Deliberately did NOT port nyblnet#466 (the embed element) despite the two sharing an isWebUrl definition upstream — that's a separate, significantly more involved and security-sensitive feature (sandboxed live iframes, embedding another deck's full JSON with its own collaboration secrets needing careful envelope-stripping, a dedicated ~400-line security test suite) that deserves its own focused pass, not a rushed inclusion here. Defined isWebUrl standalone in this fork's own model.ts instead — the two features are functionally independent; they only happened to be developed in an overlapping way upstream. The "shortcut list" third of the original PR also isn't included — a distinct, separate discoverability feature, not part of this request. ## Links An element's `link` may now be an http(s) URL as well as a slide id, and [caption](https://…) in a text box becomes a clickable <a href> — both while typing (autoformat) and when pasting markdown text. One scheme test (isWebUrl: http/https only, bounded to 2048 chars, no quote or angle bracket — blocking attribute-breakout attempts) is asked by every surface: the sanitizer (an <a> keeps its href only when it passes, unwrapped to plain text otherwise), both markdown converters, the element-link panel UI, and the show itself before it opens anything. Opening always goes through a new tab (window.open with noopener,noreferrer — the deck itself never navigates away, and the target page can't reach back into this window or see where the click came from), gated by this fork's own existing offlineEnabled() check (a toast explains why nothing opened, reusing the existing flashPresentMsg mechanism). A middle-click (auxclick, which bypasses a plain 'click' handler and the browser's own default for it on an anchor is "open immediately, past every one of those safeguards") is routed through the exact same door. rel="noopener noreferrer" is also set directly on every anchor at slide-mount time, in the show only, never persisted to the saved document — closing the gap where the browser's OWN routes to a link (its context menu, a drag) don't pass through any click handler at all and would otherwise leak this page's own location as the referrer. In the editor itself, clicking a link edits text like any other content and never navigates away — a new capture-phase click guard on the canvas stage specifically prevents that. Found and fixed a real, narrow sanitizer gap while porting this: the unknown/disallowed-element unwrap path never recursively walked its own children before lifting them out — meaning any attribute-bearing content (now including hrefs) nested inside a disallowed wrapper tag would skip sanitization entirely. Walking first, then lifting, closes that gap — directly relevant now that anchors carry a security-checked attribute worth actually validating. Scope note: this fork has no untrusted.ts (a formal shape-gate validator for pasted/CRDT-synced content) at all — a pre-existing architectural difference from upstream, not a new gap introduced by this specific change. The sanitizer (render.ts) remains the primary, already-ported defense for link content either way. ## Bullets "* " now makes a bullet exactly like "- " already did; two or more leading spaces make an indented, hollow sub-bullet (◦) — both while typing (autoformat, undoable with ⌘Z like the existing single-level bullet already was) and when pasting markdown text (indent preserved as NBSPs so it survives HTML whitespace collapsing). Caught and fixed a genuine encoding mismatch while porting this: the bullet-replacement character in this fork's OWN pre-existing code already used a non- breaking space after the glyph, not a plain space (unlike upstream's own version) — my first attempt didn't match that and had to be corrected via precise byte-level inspection (od -c) to stay consistent with this fork's own established convention rather than silently introducing a mixed-whitespace inconsistency between the old single-level bullet and the new sub-bullet. Verified: tsc -b clean, full build:single succeeds. Unit-tested isWebUrl directly against ten cases — valid http/https URLs, and every dangerous one it must reject (javascript:, data:, file:, an attribute- breakout attempt via an embedded quote, an angle bracket, an over-length string, a non-string value, and a plain slide id, which is NOT a URL and must fall through to the existing slide-jump path) — all ten matched expectations exactly. Also decompressed the actual built .bento.html to confirm the new German UI strings, the noopener/ noreferrer flags, the offline-mode message, and the sub-bullet glyph all landed in what ships.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three items from the issue/discussion scan, each small enough for this release. Needs a security pass on the links half before naming — it adds
<a href>to the text sanitizer's allowlist and opens external pages from the show.Clickable links (#421 ← discussions #373, #374; the maintainer promised it on 09-05). An element's
linkmay be an http(s) URL as well as a slide id (Presenting → Web link), and[caption](https://…)in a text box becomes an anchor. One scheme test,isWebUrl(http/https only, bounded, no quote/angle bracket), asked by all four surfaces: the shape gate, the text sanitizer (an<a>keeps its href only when it passes, and is unwrapped to text otherwise — never kept href-less), both markdown converters, and the show before it opens anything. Opening is alwayswindow.open(url, '_blank', 'noopener,noreferrer')— never a navigation of the deck;target/relare decided at click time and never stored. In the editor a link click edits text. The offline switch is honoured with a toast.Measured in Chrome against the built shell: text anchors and element links open with exactly those flags and the page stays; a
javascript:anchor is unwrapped to its text; slide links still jump; the editor never navigates; with offline mode on nothing opens.test-sanitize.tsgains the anchor checks (143/143 in the browser: a web anchor keeps exactly its href and no other attribute;javascript:/data:/attribute-less anchors become text).scripts/test-slides-links.ts(27, registered) pinsisWebUrl, both converters, and the four callers by shape.*and indented bullets (#255, #368).*makes a bullet like-; two or more leading spaces make a sub-bullet — while typing (autoformat, ⌘Z-revertible as before) and when pasting markdown (indent kept as NBSPs so it survives whitespace collapsing).Shortcut list (#269). The
?overlay now namesB,G,[], ⌘B/I/U, the zoom keys, and the arrow keys' two jobs — every one verified against a handler that exists.Also: 16 strings in all 8 catalogs and all 22 packs (4 of them pre-existing gaps from the code-snippet work — "Add a code snippet", "Source Code", the JSON-copy tooltip, the offline-mode toast; packs now 91.0%); changelog entries; a DECISIONS entry for the link decisions. Typecheck, build:single + splice gate, ci-registered 315, export-secrets, pagination, steps, clipboard 81, i18n, modelkeys all green. No CLAUDE.md/AGENTS.md change. Appends to
[Unreleased]— a one-line rebase against #463 whichever lands second.