Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions CLAUDE.md

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions dialogs.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* RDFa Editor toolbar dialog styles (insert link, table, figure).
Host UIs that provide their own form styling (e.g. Bootstrap)
should omit this file and style .edit-dialog themselves. */

/* Link / figure dialogs */
.edit-dialog {
background: white;
border: 2px solid #2196f3;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
padding: 16px;
min-width: 320px;
z-index: 10000;
}

.edit-dialog label {
display: block;
font-size: 13px;
font-weight: 500;
color: #424242;
margin: 8px 0 4px 0;
}

.edit-dialog input[type="text"] {
width: 100%;
padding: 8px 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}

.edit-dialog .rdfa-editor-ui .action-buttons {
margin-top: 14px;
}

.edit-dialog .rdfa-editor-ui .checkbox-label {
display: inline-flex;
gap: 6px;
align-items: center;
font-weight: 400;
margin-top: 8px;
}
16 changes: 8 additions & 8 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The prototype (through commit `b8466cf`) is functionally rich: structured-block
- Cross-text-node find matches (currently single-text-node only).

### A3. Robustness / quality
- **Multiple editable regions per page**: everything assumes a single `id('content')` (`local:content()`, block-of, undo snapshots, lint, ToC). LDH pages have MANY XHTML blocks. Refactor to instance-scoped editing: editable roots resolved by class/typeof convention or param; undo stack per root (or keyed snapshots); ToC/breadcrumb scoped to the active root.
- **Multiple editable regions per page**: everything assumes a single `id('content')` (`rdfae:content()`, block-of, undo snapshots, lint, ToC). LDH pages have MANY XHTML blocks. Refactor to instance-scoped editing: editable roots resolved by class/typeof convention or param; undo stack per root (or keyed snapshots); ToC/breadcrumb scoped to the active root.
- Keyboard/a11y: Escape closes dialogs/overlay; focus trap in dialogs; ARIA roles/labels on toolbar, drawer, dialogs, breadcrumb; keyboard block move (Alt+Arrow) as DnD alternative; `prefers-reduced-motion`.
- i18n: UI strings hardcoded English — externalize following LDH's translations.rdf pattern.
- Browser matrix: only Chromium is CI-verified; Firefox + Safari manual/automated passes (Playwright Firefox/WebKit installable).
Expand All @@ -33,7 +33,7 @@ The prototype (through commit `b8466cf`) is functionally rich: structured-block
### A5. Engineering hygiene
- CI: GitHub Actions running `tests/run-tests.sh` + the Playwright suites (currently scratchpad-only — move smoke scripts into `tests/browser/` in-repo).
- Packaging: CSS lives in the demo `index.html` — extract `rdfa-editor.css`; demo page vs library separation; README/integration docs; versioning.
- Namespace: `local:`/`urn:rdfa-editor:*` fine internally; consider a stable public namespace for a distributable component.
- Namespace: done — `rdfae:` = `https://w3id.org/atomgraph/rdfa-editor#` (with `content-model#` and `lint#` sub-namespaces); register the `rdfa-editor` redirect at perma-id/w3id.org.

## B. LinkedDataHub embedding contract (WYMEditor replacement) — ships as `docs/ldh/MIGRATION.md` in this repo

Expand All @@ -54,7 +54,7 @@ Mapped integration surface (all in `LinkedDataHub/src/main/webapp/static/com/ato

- **M1 — Hardening (THIS ROUND, implemented)**: sanitization + HTML paste + a11y/keys + undo caret restoration + in-repo tests/CI + CSS extraction + README; migration plan document in `ldh/`.
- **M2 — Multi-instance component** (DONE): `.rdfa-editor-content` regions, region-keyed undo, scoped ToC/source, `rdfaEditor*` state prefix, `.rdfa-editor-ui` CSS scoping; LDH integration compile-proven (docs/ldh/MIGRATION.md §10).
- **Tables** (DONE): composite table blocks — rows×cols insert dialog (optional header row + caption), positional row/column operations gated on `local:has-spans`, Tab/Shift+Tab + Enter cell traversal that grows the grid at its bottom edge (`src/tables.xsl`).
- **Tables** (DONE): composite table blocks — rows×cols insert dialog (optional header row + caption), positional row/column operations gated on `rdfae:has-spans`, Tab/Shift+Tab + Enter cell traversal that grows the grid at its bottom edge (`src/tables.xsl`).
- **M3 — LDH swap**: the contract above (LDH-side patches + build wiring + e2e in an LDH dev instance).
- **M4 — Vocabulary UX**: typeahead over ontology terms from `/ns`, schema.org vocab, domain/range-aware ranking.
- **M5 — Editing completeness**: nested lists, h4–h6, code/sub/sup, image upload via LDH, cross-node find, i18n strings, Firefox/Safari passes, touch fallback.
Expand All @@ -78,20 +78,20 @@ Mapped integration surface (all in `LinkedDataHub/src/main/webapp/static/com/ato
- Wrap stray top-level inline runs: `for-each-group group-adjacent="boolean(self::p|self::h1|…block…)"` → non-block groups wrapped in `<p>`.
- Re-materialize XDM → live DOM: `serialize()` the fragment → `$stage := createElement('div')` + `innerHTML` (safe post-sanitization).
- Insert:
- **Inline-only** fragment: `range.deleteContents()`; move `$stage` childNodes into a `createDocumentFragment` (counted `firstChild` loop, order preserved); capture `$last` ref before the move; `range.insertNode($frag)`; caret after `$last` via `local:place-caret(parent, count(preceding-sibling)+1)`.
- **Blocks into p/h/blockquote host**: push-undo; `local:split-block` at caret; insert pasted blocks after the first half (`xsl:iterate` anchor pattern from section drop); `local:init-block` each (editable + chrome); `local:ensure-placeholder` on halves; caret at end of last inserted block.
- **Inline-only** fragment: `range.deleteContents()`; move `$stage` childNodes into a `createDocumentFragment` (counted `firstChild` loop, order preserved); capture `$last` ref before the move; `range.insertNode($frag)`; caret after `$last` via `rdfae:place-caret(parent, count(preceding-sibling)+1)`.
- **Blocks into p/h/blockquote host**: push-undo; `rdfae:split-block` at caret; insert pasted blocks after the first half (`xsl:iterate` anchor pattern from section drop); `rdfae:init-block` each (editable + chrome); `rdfae:ensure-placeholder` on halves; caret at end of last inserted block.
- **Into li/figcaption**: flatten to `string($stage)` through the plain-text path (documented).
- Single push-undo before, after-mutation after; one Ctrl+Z reverts the paste.

### D3. A11y + keyboard
- **Escape** closes: keydown templates on `#overlay` and the three dialogs (input events bubble to the containers) → preventDefault + `local:hide-overlay`/`local:hide-dialogs`.
- **Escape** closes: keydown templates on `#overlay` and the three dialogs (input events bubble to the containers) → preventDefault + `rdfae:hide-overlay`/`rdfae:hide-dialogs`.
- **Alt+ArrowUp/Down moves the current block** (keyboard DnD alternative): new dispatcher branch BEFORE the plain-arrow branch (plain arrows must also gain a `not(altKey)` guard); `before(prev)`/`after(next)` sibling move; push-undo + after-mutation; caret survives (node refs move with the block); no sibling → no-op.
- **ARIA** in render templates: `role="dialog" aria-modal="true" aria-label` on overlay + dialogs; `aria-label` mirroring `@title` on toolbar buttons + `role="toolbar"` on `#edit-toolbar`; `role="navigation" aria-label` on `#toc-drawer` and `#breadcrumb`; lint badge becomes a real `<button class="lint-badge">`.
- **Focus return**: hide-overlay/hide-dialogs focus the `activeBlock` host when present. Full focus trap out of scope (documented).

### D4. Undo caret restoration (`src/undo.xsl`)
- `local:push-undo` (and both apply-undo/redo current-state pushes) capture the caret when the selection anchor is a TEXT node inside content, as data attrs on the stash entry (stash lives outside #content): `data-block` (index among content children), `data-node` (index among the block's chrome-free descendant text nodes), `data-offset`.
- `local:restore-snapshot` resolves `content/*[$bi]` → chrome-free `text()[$ni]` → `collapse(min(offset, length))`; bare-`[$index]` predicates; fallback = current first-host behavior. Caret stored with a snapshot = caret when that state existed → symmetric for undo and redo.
- `rdfae:push-undo` (and both apply-undo/redo current-state pushes) capture the caret when the selection anchor is a TEXT node inside content, as data attrs on the stash entry (stash lives outside #content): `data-block` (index among content children), `data-node` (index among the block's chrome-free descendant text nodes), `data-offset`.
- `rdfae:restore-snapshot` resolves `content/*[$bi]` → chrome-free `text()[$ni]` → `collapse(min(offset, length))`; bare-`[$index]` predicates; fallback = current first-host behavior. Caret stored with a snapshot = caret when that state existed → symmetric for undo and redo.

### D5. Tests in-repo, CI, packaging
- `tests/browser/{editor,features,fixes}.mjs` (ported suites; `PORT`/`BASE_URL` env); `package.json` (private; devDep `playwright`; scripts `test` → run-tests.sh, `test:browser`).
Expand Down
14 changes: 7 additions & 7 deletions docs/ldh/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Today: `ldh:RenderRowForm` on `textarea.wymeditor` calls the jQuery plugin

Replacement: an `ldh:RenderRowForm` template on `div.rdfa-editor-content` running the
editor's per-instance init (per-block-kind `contenteditable`, chrome injection) — the
M2 equivalent of `local:init-editing` scoped to this container. **No JS assets**: the
M2 equivalent of `rdfae:init-editing` scoped to this container. **No JS assets**: the
editor is compiled into the client SEF; only `rdfa-editor.css` is needed.

### 3. Submit sync — `ldh:FormPreSubmit` (form.xsl:178-198)
Expand Down Expand Up @@ -134,7 +134,7 @@ single SEF via `xslt3-he -nogo -ns:##html5 -relocate:on` — no errors, no confl
Audit results:
- Extractor entry is a **named template only** (`extract-rdfa`; the unnamed-mode
`match="/"` was removed; headless tests invoke with `-it:extract-rdfa`). All other
editor matching lives in named modes (`rdfa:extract`, `canonical`) or `ixsl:*` event
editor matching lives in named modes (`rdfax:extract`, `canonical`) or `ixsl:*` event
modes.
- Event templates: LDH has no `contenteditable` usage and no `body` keydown template
(only `body` onmousemove in navigation.xsl — different mode). No pattern overlap.
Expand Down Expand Up @@ -186,7 +186,7 @@ Placement is carried by the XHTML structure, the reference by the name itself; t
RDFa extraction of an empty `div[@about]` is zero triples, so no scaffolding enters
the content graph. The fragment rule disambiguates for free: fragment `@about` =
document part (a defined block or annotated content), absolute non-document `@about`
on an effectively-empty div = dereference and render (`local:reference-block` in
on an effectively-empty div = dereference and render (`rdfae:reference-block` in
src/blocks.xsl). What the wrapper used to pay for — per-embedding metadata such as
`ac:mode` — has no subject in this idiom; if per-embed rendering modes return, they
need a typed block again.
Expand All @@ -206,18 +206,18 @@ example below works the same way) with the ephemeral rendering div as the contai
<!-- client.xsl (imports the editor modules, higher import precedence) -->
<xsl:param name="object-block-types" as="xs:string*"
select="('&ldh;View', '&ldh;ResultSetChart', '&ldh;GraphChart')"/>
<!-- reference blocks (embed-by-URI) need no param entry: local:reference-block
<!-- reference blocks (embed-by-URI) need no param entry: rdfae:reference-block
recognizes them structurally; client.xsl renders them by dereferencing
@about into its resource-rendering machinery -->


<xsl:template match="div[tokenize(@typeof) = ('&ldh;ResultSetChart', '&ldh;GraphChart')]"
mode="local:render-island">
mode="rdfae:render-island">
<xsl:variable name="island" as="element()" select="."/>
<!-- the container ldh:RenderRow renders into: the ephemeral rendering div
(canonicalization-stripped, extractor-skipped). Seed it with the LDH
progress-bar markup; the thunk chain replaces it with the chart -->
<xsl:call-template name="local:replace-rendering">
<xsl:call-template name="rdfae:replace-rendering">
<xsl:with-param name="island" select="$island"/>
<xsl:with-param name="content">
<div class="progress progress-striped active">
Expand Down Expand Up @@ -246,7 +246,7 @@ The existing async pipelines (`ldh:chart-self-thunk` → `ldh:chart-query-thunk`
container-plus-context-map based, and literal definition values are span text
content per the v6 format, exactly what their selectors read. One caveat: chart
canvases don't survive an `innerHTML` undo restore; the editor re-fires
`local:render-island` only for islands *without* a rendering div, so a
`rdfae:render-island` only for islands *without* a rendering div, so a
canvas-bearing bridge should also re-draw from the `LinkedDataHub.contents` cache
when its canvas is dead (LDH already does this on mode switches).

Expand Down
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
}
</script>
<link rel="stylesheet" href="rdfa-editor.css" type="text/css"/>
<link rel="stylesheet" href="overlay.css" type="text/css"/>
<link rel="stylesheet" href="toolbar.css" type="text/css"/>
<link rel="stylesheet" href="dialogs.css" type="text/css"/>
<style type="text/css">
html, body { margin: 0; }
body {
Expand Down
Loading
Loading