mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
5a41a21861
* fix(ui): in-page anchor navigation for headings
Intercept `#`-prefixed link clicks and smooth-scroll inside the sticky
scroll viewport instead of letting the browser jump (which broke when the
content lives inside a scrollable container). Headings now expose both a
canonical slug and a legacy slug via `data-anchor-aliases` so older shared
URLs keep resolving. Also listen for `hashchange` / initial hash so direct
links land on the right heading.
Adds `packages/ui/utils/anchors.ts` with `normalizeAnchorText`,
`decodeAnchorHash`, `slugifyHeadingAnchor`, `legacySlugifyHeadingAnchor`,
and `getHeadingAnchorAliases` (+ tests).
Also ignore `.serena/` tooling state.
* refactor(ui): drop unjustified slug aliasing
Anchor navigation didn't work before this branch, so no shared URLs exist
using any slug format — the legacy slug alias was preserving nothing.
The canonical slug was also a redefinition: heading ids are already
written by `utils/slugify.ts` (unicode-aware, `\p{L}\p{N}`). The new
ASCII-only `slugifyHeadingAnchor` would miss unicode headings ("Café"
writes `id="café"` but the nav code looked for `cafe`).
Nav path is now just: decode the hash and `getElementById`. The
sticky-header offset math — the part that actually mattered — stays.
* fix(ui): anchor-nav edge cases — share-hash collision and raw HTML blocks
Two issues flagged by cross-model review of 9978a40:
1. `useSharing` treated any non-empty `window.location.hash` as a share
payload. Plain anchor hashes like `#section-overview` fell into
`parseShareHash`, failed decompression, and popped the "Shared Plan
Could Not Be Loaded" dialog. Added an `isPlainAnchorHash` guard
(lowercase ASCII + digits + hyphen) that short-circuits before the
share path in both the initial load and the hashchange listener.
Share payloads are base64url-encoded deflate output, so they'll
always contain uppercase/`=`/`_` and never match the guard.
2. `HtmlBlock` (raw `<details>`, `<summary>`, etc.) skipped the new
nav handler. `rewriteRelativeRefs` returned early for any `#` href,
so anchors inside raw HTML fell back to native browser jump which
doesn't target the OverlayScrollArea. Now intercepts those clicks
and routes through `onNavigateAnchor` to match the `InlineMarkdown`
path. Threaded the callback through `BlockRenderer`.
* fix(ui): widen share-payload detection to cover Unicode + uppercase ids
Previous guard rejected any hash that wasn't lowercase ASCII + digits +
hyphen, so Unicode heading ids (`#café`, `#中文-标题`) and raw HTML ids
(`#MySection`) still fell into `parseShareHash` and popped the shared-
plan error dialog.
Flip the predicate: a share payload is base64url (`[A-Za-z0-9_-]`),
realistically ≥30 chars, and virtually always contains at least one
uppercase letter because deflate output has high entropy. Anything else
is handed back to Viewer to scroll to (or silently ignored).
Also add tests/test-fixtures/14-anchor-links.md exercising the full
character range — ASCII, Unicode (accented, CJK, Cyrillic), numeric
start, raw HTML ids — across paragraph, list, blockquote, alert,
directive, table, and raw `<details>` contexts.
12 lines
272 B
TypeScript
12 lines
272 B
TypeScript
export function decodeAnchorHash(hash: string): string | null {
|
|
const raw = hash.replace(/^#/, '').split('?')[0]?.trim();
|
|
if (!raw) return null;
|
|
|
|
try {
|
|
const decoded = decodeURIComponent(raw).trim();
|
|
return decoded || null;
|
|
} catch {
|
|
return raw;
|
|
}
|
|
}
|