From 0496acfbb86a8f80dfbaad61783918e8f2bf72c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Mon, 13 Jul 2026 11:24:23 +0300 Subject: [PATCH 1/2] Embed LDH's RDF-defined blocks as atomic RDFa islands Blocks defined purely in RDF (ldh:View, ldh:ResultSetChart, ldh:Object) can now live wherever the content model admits a div, per the LDH v6 document format: a div[@about][@typeof] block element with its definition triples as span[@property] children (objects via @resource, literals as span text) - no wrappers, no rdf:_N sequences, no document-to-block containment edges. The editor treats such a div as an atomic island: never a text host, focusable/navigable/deletable like a block image (shared local:select-island doctrine, undo/redo chords included), a hard boundary for merges (merge-host-in gains a tail-island guard - islands hold no hosts, the lookup would leapfrog them) and for the delete machine (clamped-range moves boundaries out of islands, so they join selections whole and are only ever removed whole), and byte-identical on round-trip: the visual rendering is injected into an ephemeral div[data-role=rendering] child by the overridable mode="local:render-island" hook and stripped at canonicalization. The core (new src/blocks.xsl: $object-block-types param, the local:island predicate, the render hook + neutral card, extension stubs) knows no LDH vocabulary. The LDH extension (src/ldh-blocks.xsl, layered by the src/ldh-editor.xsl entry via xsl:import) supplies the class list, async renderers - ixsl:promise/ixsl:http-request chains fetching the referenced documents by content negotiation on clean trailing-slash URIs (no file extensions anywhere; serve.mjs replaces python http.server and negotiates rdf+xml / sparql-results+xml representations) - and the Block... insert dialog wired into the slash menu and toolbar through the new hook points. In production LinkedDataHub, client.xsl plays the extension's role, bridging the hook into its block rendering (MIGRATION.md par. 12). Two build flavors: the core SEF (dist/index.xsl.sef.json) is what GitHub Pages deploys - Pages cannot content-negotiate, so the LDH-extended SEF (dist/ldh-editor.xsl.sef.json) and the demo/ showcase page stay local-only. New coverage: extractor/canonical/lint fixtures for the storage form, tests/browser/blocks.mjs against tests/fixture-blocks.html (hydration, round-trip, navigation, boundaries, dialog insertion top-level and nested, stage-2/sweep atomicity, canonical copy, undo); fixture-nesting.html runs the extended SEF so every core suite doubles as proof the extension layers non-invasively. Co-Authored-By: Claude Fable 5 --- .github/workflows/deploy-pages.yml | 4 + CLAUDE.md | 26 +- LDH-BLOCKS-PLAN.md | 181 +++++++ Makefile | 4 +- README.md | 20 +- demo/index.html | 121 +++++ demo/queries/population/index.rdf | 19 + demo/queries/population/results.xml | 29 + demo/resources/ada/index.rdf | 14 + docs/ldh/MIGRATION.md | 78 +++ generate-sef.sh | 17 +- rdfa-editor.css | 28 + serve.mjs | 53 ++ src/blocks.xsl | 103 ++++ src/edit.xsl | 174 ++++-- src/index.xsl | 2 + src/input.xsl | 12 +- src/ldh-blocks.xsl | 504 ++++++++++++++++++ src/ldh-editor.xsl | 20 + src/select.xsl | 35 +- src/undo.xsl | 6 + tests/browser/blocks.mjs | 311 +++++++++++ tests/browser/notion.mjs | 8 +- tests/browser/run.mjs | 32 +- tests/expected/14-ldh-block-island.rdf | 21 + .../canonical/23-ldh-block-placeholder.xhtml | 16 + tests/expected/lint/09-ldh-block-clean.txt | 0 tests/fixture-blocks.html | 62 +++ tests/fixture-nesting.html | 2 +- tests/fixtures/14-ldh-block-island.xhtml | 20 + .../canonical/23-ldh-block-placeholder.xhtml | 24 + tests/fixtures/lint/09-ldh-block-clean.xhtml | 18 + 32 files changed, 1878 insertions(+), 86 deletions(-) create mode 100644 LDH-BLOCKS-PLAN.md create mode 100644 demo/index.html create mode 100644 demo/queries/population/index.rdf create mode 100644 demo/queries/population/results.xml create mode 100644 demo/resources/ada/index.rdf create mode 100644 serve.mjs create mode 100644 src/blocks.xsl create mode 100644 src/ldh-blocks.xsl create mode 100644 src/ldh-editor.xsl create mode 100644 tests/browser/blocks.mjs create mode 100644 tests/expected/14-ldh-block-island.rdf create mode 100644 tests/expected/canonical/23-ldh-block-placeholder.xhtml create mode 100644 tests/expected/lint/09-ldh-block-clean.txt create mode 100644 tests/fixture-blocks.html create mode 100644 tests/fixtures/14-ldh-block-island.xhtml create mode 100644 tests/fixtures/canonical/23-ldh-block-placeholder.xhtml create mode 100644 tests/fixtures/lint/09-ldh-block-clean.xhtml diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 37a0f5d..0ec0a48 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -27,6 +27,10 @@ jobs: mkdir -p _site cp index.html rdfa-editor.css _site/ cp -r lib dist vocabs _site/ + # core editor only: the LinkedDataHub-extended flavor (demo/ + + # its SEF) needs the content-negotiating dev server (serve.mjs) + # and is never deployed to Pages + rm -f _site/dist/ldh-editor.xsl.sef.json - name: Publish to gh-pages branch uses: peaceiris/actions-gh-pages@v4 with: diff --git a/CLAUDE.md b/CLAUDE.md index c064720..859f7d3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,25 +13,29 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Overview -RDFa-Editor is a prototype XHTML+RDFa authoring tool using client-side XSLT. Content lives in a `#content` container of structured blocks (p, h1–h3, ul/ol, blockquote, pre, figure, table), always editable Notion-style: typing within blocks, Enter splits, Backspace-at-start merges, toolbar for block types / inline formatting / lists / figures / tables / links, drag-handle reordering, a `/` slash menu (in an empty block) and markdown shortcuts (`# `, `- `, `1. `, `> `, ` ``` `) that insert/convert blocks per the content model. Nesting follows the **XHTML Strict content model** (see content-model.xsl below): `blockquote` is a block *container* (`blockquote > p+` — bare text inside it is invalid and gets normalized; the toolbar quote toggle wraps/unwraps), `li`/`td`/`th`/`dd`/`figcaption` are `%Flow;` (mixed text AND nested blocks — Tab/Shift+Tab indent/outdent nested lists), `p`/`h1–h6`/`caption`/`dt` are inline-only. Tables are composite blocks (structure locked; `td`/`th`/`caption` editable) inserted via a rows×cols dialog with an optional header row and caption; row/column toolbar controls, Tab/Shift+Tab cell traversal and Enter (step down a column) all grow the grid at its bottom edge. Right-click a text selection to annotate it with RDFa; right-click an existing annotation to edit or remove it. "Extract RDF" shows the page's triples; "Source" shows the canonical XHTML+RDFa serialization (all editing ephemera stripped). +RDFa-Editor is a prototype XHTML+RDFa authoring tool using client-side XSLT. Content lives in a `#content` container of structured blocks (p, h1–h3, ul/ol, blockquote, pre, figure, table), always editable Notion-style: typing within blocks, Enter splits, Backspace-at-start merges, toolbar for block types / inline formatting / lists / figures / tables / links, drag-handle reordering, a `/` slash menu (in an empty block) and markdown shortcuts (`# `, `- `, `1. `, `> `, ` ``` `) that insert/convert blocks per the content model. **Object blocks** (blocks.xsl) embed RDF-defined blocks per the LDH v6 document format (`XHTML-RDFa-as-LDH-v6-Document-Format` wiki) — a `div[@about][@typeof]` whose `@typeof` matches the `$object-block-types` param, its defining triples inline as `span[@property]` children — treated as **atomic islands** (never editable inside, focusable/navigable/deletable like block images, hard merge/delete boundaries) and rendered client-side into an ephemeral `div[@data-role='rendering']` child via the overridable `mode="local:render-island"` hook; the LinkedDataHub extension (ldh-blocks.xsl, layered by the ldh-editor.xsl entry via `xsl:import`) supplies `ldh:Object`/`ldh:View`/`ldh:ResultSetChart` renderers, an insert dialog and slash/toolbar contributions. Nesting follows the **XHTML Strict content model** (see content-model.xsl below): `blockquote` is a block *container* (`blockquote > p+` — bare text inside it is invalid and gets normalized; the toolbar quote toggle wraps/unwraps), `li`/`td`/`th`/`dd`/`figcaption` are `%Flow;` (mixed text AND nested blocks — Tab/Shift+Tab indent/outdent nested lists), `p`/`h1–h6`/`caption`/`dt` are inline-only. Tables are composite blocks (structure locked; `td`/`th`/`caption` editable) inserted via a rows×cols dialog with an optional header row and caption; row/column toolbar controls, Tab/Shift+Tab cell traversal and Enter (step down a column) all grow the grid at its bottom edge. Right-click a text selection to annotate it with RDFa; right-click an existing annotation to edit or remove it. "Extract RDF" shows the page's triples; "Source" shows the canonical XHTML+RDFa serialization (all editing ephemera stripped). ## Architecture -XSLT 3.0 running on **SaxonJS 3** with its interactive extensions (`ixsl:` namespace). `index.html` loads `lib/SaxonJS3.js` and executes `dist/index.xsl.sef.json` (initial template `main`). The editor UI is fully generated by the stylesheet — the overlay is rendered once at startup (hidden) and only populated/shown/hidden afterwards, so the stylesheet is self-contained and embeddable on any host page. +XSLT 3.0 running on **SaxonJS 3** with its interactive extensions (`ixsl:` namespace). `index.html` loads `lib/SaxonJS3.js` and executes `dist/index.xsl.sef.json` (initial template `main`); async work uses the **SaxonJS 3 promise API** (`ixsl:promise` + `ixsl:http-request` `=> ixsl:then`, `on-failure`; DOM-mutating callbacks are `ixsl:updating="yes"` functions) — **never the legacy `ixsl:schedule-action`**. The editor UI is fully generated by the stylesheet — the overlay is rendered once at startup (hidden) and only populated/shown/hidden afterwards, so the stylesheet is self-contained and embeddable on any host page. + +Two build flavors: **core** (`src/index.xsl` → `dist/index.xsl.sef.json`, deployed to GitHub Pages — `deploy-pages.yml` excludes the LDH artifacts) and **LDH-extended** (`src/ldh-editor.xsl` → `dist/ldh-editor.xsl.sef.json`, used by `demo/index.html` and the browser fixtures — local only, its object blocks need the content-negotiating dev server). Modules under `src/`: -- **index.xsl** — entry point: `$vocab-hrefs` param, `main` template (async vocabulary prefetch via `ixsl:schedule-action/@document`, then overlay init), `local:document-uri()`. +- **index.xsl** — the core entry point: `main` template (fully synchronous — the host page preloads the vocabularies into the SaxonJS `documentPool` keyed by page-relative URI, see index.html), editor-state container init, `local:editor-state()`; the module include list. - **RDFa2RDFXML-v3.xsl** — RDFa 1.1 → RDF/XML extraction. **Pure XSLT, no `ixsl:`** — it runs headless via `xslt3` for the test suite. Strictly W3C-conformant (RDFa 1.1 processing rules, section 7.5, including chaining via typed resources and `rdfa:usesVocabulary`). Covers `@about`/`@typeof`/`@property`/`@content`/`@datatype`/`@resource`/`@href`/`@src`/`@prefix` (and `xmlns:*`)/`@vocab` with bare terms, `@lang`+`@xml:lang` inheritance, base-URI resolution (`about=""` = the document, `` honored), and exclusion of `head`/`script`/`style`/`[data-role='rendering']` subtrees. Out of scope (documented future work): `@rel`/`@rev`, `@inlist`, safe CURIEs, `@datetime`/`