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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Modules under `src/`:
- **vocab.xsl** — generic ontology RDF/XML → term maps (`local:vocab-terms` → `{uri, label}`, consumed by the typeahead). Handles typed elements (`<owl:Class rdf:about>`) and `rdf:Description`+`rdf:type` shapes (including striped one-triple-per-description files like DCMI terms); labels as attributes or child elements; terms outside the ontology namespace filtered out.
- **overlay.xsl** — the annotation form (single copy), form reading (`local:form-values` map), pre-filling (`local:populate-form`), show/hide with viewport clamping (`local:show-at`, shared with the edit dialogs). The property/type fields are typeaheads (typeahead.xsl); the datatype field stays a `<select>` (short XSD list) with a `urn:rdfa-editor:custom` sentinel + free-text `custom-datatype` input and the datatype↔lang mutual-exclusion handler.
- **typeahead.xsl** — autocomplete lookups for the **property** and **type** fields (replacing the long dropdowns; LDH's typeahead interaction ported with local synchronous filtering — no SPARQL/debounce, since vocabularies are already pooled). A stable wrapper `span[@data-field]` holds one of two states, swapped via `result-document` literal result elements: typing (`input.typeahead-input` + `ul.typeahead-menu`) or committed (`button.typeahead-value` + hidden input carrying the IRI, LDH-style). Filtering re-renders per keystroke from `local:vocab-terms` (prefix-ranked, capped, bold-highlighted match); keyboard nav mirrors the slash menu (Arrow/Enter/Escape, first option pre-selected); options commit on `ixsl:onmousedown` (before the input's `focusout`, children `pointer-events:none` so the `<li>` is the target). `local:typeahead-value` reads the committed hidden IRI, else a free-typed absolute IRI (`local:is-absolute-iri`), else the untouched button→edit value parked on `@data-editing-iri` (cleared on the first keystroke — the stale-selection guard). Escape closes the menu else the overlay (the innermost template fully owns the input's keys).
- **annotate.xsl** — the unified right-click dispatcher (edit vs create), `local:apply-annotation` (single write path for RDFa attributes), shared `local:wrap-range`/`local:unwrap-element` (used by annotations AND inline formatting), invalid-selection flash, the shared output modal (`local:show-output`).
- **annotate.xsl** — the unified right-click dispatcher (edit vs create), `local:apply-annotation` (single write path for RDFa attributes), shared `local:wrap-range`/`local:unwrap-element` (used by annotations AND inline formatting), invalid-selection flash, the shared output modal (`local:show-output`; callers passing a filename get a Download button — the shown text becomes a client-side Blob object-URL download, so it works on static hosting like gh-pages; the lint modal passes none and hides it).
- **edit.xsl** — the XHTML editor. **Recursive editability init** (`local:init-block`): an element is a *text host* (contenteditable) while its content model allows text and it holds no block children; mixed flow containers (`li`/`td`/`dd`/… with blocks) and structural containers (blockquote, lists, figure, table) lock their own markup and recurse into their parts (`figure` is always composite: image island + caption host); `img` never editable but given `tabindex="-1"` so a block image is a focusable **navigation island**. **Run wrappers**: the stray inline runs of a mixed flow container (`<li>text<ul>…</ul></li>`) get an ephemeral `p.rdfa-editor-run` host (`local:wrap-stray-runs`) so they stay editable — unwrapped again at canonicalization (C11), so mixed content round-trips byte-identical; structural gestures promote a wrapper to a real paragraph. **Load-init normalization**: each region is probed for invalid nesting (`cm:valid-nesting`, stray text in structural containers, stray region-level inline) and rebuilt through `mode="cm-normalize"` only when invalid (the valid case is zero-churn; host-page node references into a rewritten region go stale, same as undo restore). First-child chrome injection covers **every draggable block** (`local:draggable-block`: a real `%block` — never a run wrapper, `hr` or ephemera — whose parent is a region, a `cm:flow` container or a `blockquote`): nested blocks carry their own handles, stacked per nesting level, each hover-revealed at its block's own gutter indent (the editing view indents `blockquote` content so quote-child handles don't coincide with the quote's); `local:ensure-chrome` in `local:after-mutation` converges handles after every gesture, so mutation sites never thread their own injection. The Enter/Backspace state machine (split/merge primitives with explicit caret placement): splitting a run wrapper *promotes* it (marker class off); E4a — Enter on the empty last item of a *nested* list outdents one level (progressive), E4b — top-level exits to a paragraph; B2 merges text blocks, B2b descends into a preceding non-composite container's (blockquote/list) last host — bare text in the container itself would be invalid — while tables/figures stay hard boundaries (B3); B4 merges an `li` into the *visually preceding line* (`local:merge-host-in` — the last host of the previous item, unless it sits inside a nested table/figure: composites are hard boundaries at any depth, the gesture stays inert), B4b outdents the first item of a nested list, B4c merges the first item of a container-nested list (cell, quote, dd) into the preceding line inside the container — dissolving the emptied list and collapsing the container back to a text host — B7 exits the first block of a blockquote upward (emptied quote removed); E4b anchors the list-exit paragraph *before* removal, so it lands where the list was (inside the cell/quote for nested lists); E6 anchors the caption-exit paragraph after the figure/table itself (which may be nested). **Ancestor walks clamp at the region root** (`[exists(local:block-of(.))]` on `local:item-of`, the Tab cell lookup and the quote-toggle's blockquote lookup) — a host-page li/td/blockquote wrapping an embedded region is never touched. A focused image island deletes its *figure* when it has one, else just the image — never the whole containing list/table. **Tab/Shift+Tab** indent/outdent list items (`local:list-indent`/`local:list-outdent`: indent moves the item into a trailing-or-fresh nested list *inside the previous item* per `ul → (li)+`; outdent moves it after the container item, demoting followers into a nested list inside it; `local:collapse-container` reverts an emptied container to a text host; extremes flash) — the innermost context wins over table-cell traversal (li-in-td indents, a table nested in an item traverses). The **quote toggle** (`format-quote`) wraps the current host block in a `blockquote` where the parent admits one / unwraps all children out (refused with a flash when the quote carries RDFa — triples would drop); the block-type select is **host-based** (`local:current-host`): a paragraph inside a quote converts alone, non-convertible hosts disable it. The **insert buttons** (+¶, lists) place the new block per the content model (`local:insert-block-at-caret`): after the caret's host when its parent admits the kind, *inside* a leaf flow host otherwise (a list inserted in a cell nests in the cell; in a list item it becomes a sublist), after the top-level block as a last resort. **Paste** is cm-driven: clipboard HTML runs `mode="canonical"` + `mode="cm-normalize"`, then blocks land *inside* a mixed flow host (`local:paste-into-flow-host` — head/tail runs wrapped, host re-inited as container), or as siblings of an inline-only host via split-and-thread (when `cm:allows-child` permits), or flatten to text (caption, dt). Drag-and-drop ported from LinkedDataHub's `client/block.xsl` (handle-gated `draggable`, midpoint before/after marks) and extended to **nested blocks with a content-model-validated drop resolver**: the target is the innermost draggable block under the pointer whose container accepts the dragged kind (`local:deepest-block-at` + `local:legal-drop-level` climbing to the nearest legal level — the region accepts any block, so the climb terminates and an illegal drop is *clamped*, never created); gutter/gap drops fall back to the nearest top-level block, so dropping in the left gutter lifts a nested block all the way out; **cells take "into" drops** (nothing can legally drop before/after a `td`/`th`, so a cell's pixels away from any block it holds mean *into it* — the text host becomes a container per the `local:insert-block-at-caret` flow doctrine, marked by an inset `drop-into` box; a list item deliberately stays a clamp to around-the-list, since its pixels compete with reordering); the vacated origin is repaired at commit (`local:prune-husks` for emptied structural containers — the B7 doctrine — then `local:collapse-container` for a flow container that lost its last block); transient drag state (`dragging`/`draggable`/empty `class` leftovers — `local:tidy-class`) is stripped before the undo snapshot; drops stay region-scoped (blocks never move between regions). Arrow-key block crossing walks `local:nav-targets` (editable hosts **plus** block images, in document order): a host gets a caret, an image is *selected* (`local:select-image` focuses it, clearing the caret) — so images are never skipped; on a selected image, arrows step to the adjacent target and Backspace/Delete removes the whole figure (no confirm, undo-covered). Table cell keys (Tab/Enter) and the table dialog live in **tables.xsl**.
- **tables.xsl** — table blocks as a composite kind: the insert dialog (rows×cols + header-row + caption, modeled on the figure dialog), positional row/column operations (`local:op-insert-row`/`-column`, `-delete-row`/`-column`) gated behind `local:has-spans` (disabled on pasted `colspan`/`rowspan` grids — positional edits would corrupt a spanned table) and toolbar-synced via `local:sync-table-toolbar` riding `local:update-breadcrumb`, plus `local:table-tab`/`local:table-enter` cell traversal that appends a body row at the bottom edge. Cells are `%Flow;` containers: every caret landing resolves through `local:first-host-in`/`local:last-host-in`, so a cell holding blocks lands the caret in its first/last editable host; the Tab dispatcher (edit.xsl) passes the *cell* as the traversal host even when the caret sits in a nested block. Deleting the last body row/column is a no-op — the confirm-guarded delete-block button removes the whole table. **Undo hazard:** a chrome `span` serialized as a direct child of a (possibly nested) `<table>` is foster-parented out by the HTML fragment parser on `innerHTML` restore, so `local:restore-snapshot` strips every handle and re-converges via `local:ensure-chrome` (deterministic first-child prepend) before caret resolution.
- **select.xsl** — the Docs-style selection gesture layer, region-scoped select-all and cross-host selection delete (keyboard dispatch lives in edit.xsl's keydown/body/paste templates, mirroring the tables.xsl split; the mouse gesture templates live here). **The browser confines a native drag-selection to the host it starts in** (even background-origin drags clamp on the first host entered), so cross-block selection is **synthesized**: mousedown in a region arms a sweep anchor (`local:caret-at-point` — `caretRangeFromPoint`, `caretPositionFromPoint` fallback; chrome positions escape to just after the chrome); once the pointer leaves the anchor host, each `body|html` mousemove rebuilds the selection anchor→pointer via `setBaseAndExtent` — the only Selection API that can express a **backward selection** (upward drags keep the anchor fixed) — with the focus clamped into the anchor's region (`local:clamp-focus-to-region`, `comparePoint`-based) and a viewport nudge near its edges (native autoscroll dies with the takeover); in-host moves stay native. Mouseup disarms (innermost-match dispatch: the host/drag-handle mouseup templates disarm on their paths, `body|html` catches background ends) and `ondragstart` disarms defensively; a press on the drag handle never reaches the arm template. **Shift+Click** extends from the standing selection anchor to the clicked point (preventDefault keeps the anchor; re-arms so Shift+drag keeps extending; repeated Shift+Clicks share the anchor). **Shift+Up/Down** extend block-granularly past host edges (`local:shift-arrow-extends` gates: cross-host selection, or focus at the host edge facing the arrow — probed from the *focus*, not the range end; `local:extend-selection-block-wise` steps the focus between region-level child positions, whole blocks and composites as units, flipping direction across the anchor); within the host they stay native, Shift+Left/Right stay native throughout. Sweep state (`sweepAnchorNode/-Offset/-Host`, `sweepRegion`) lives on the editor-state container and is cleared on undo restore. **Two-stage Ctrl/Cmd+A** (Docs-style): stage 1 stays native — the browser scopes select-all to the focused host; stage 2 (host already fully selected per the chrome-aware `local:at-*` probes, or empty, or the selection already spans hosts) selects all blocks of the region as a **document-level range** (`local:select-region`) — it paints natively across host boundaries and never reaches the host page. Ctrl+A away from a caret also selects editor content, never the page: from the body it targets the swept region, else `local:active-root()` (selection anchor → last focused host → first region), and a focused image island is its own fully-selected unit (stage 2 directly, with Backspace/Delete then running the delete machine instead of the island's figure-delete); native page select-all happens only when no region exists. One **delete machine** (`local:delete-cross-host-selection`) serves stage-2 selections and every synthetic gesture alike (`local:selection-crosses-hosts()` gates it), fired by Backspace/Delete from host *or* body focus — a sweep from the page background leaves focus on body. Deletion is **block-granular**, never one raw `deleteContents` across the range: fully covered blocks are removed whole; partial edge hosts get sub-range deletes scoped *inside* the host; composites holding a range boundary never lose structure — their covered cells are cleared and flow cells collapse back to text hosts (`local:clear-host`; B3/B4 doctrine at partial coverage) while a *fully* covered composite is removed whole; non-composite edge remnants merge Docs-style via `local:merge-into-previous` with the caret at the seam (never with `pre` — B6); emptied structural containers are pruned (`local:prune-husks`), chrome is re-injected (idempotent), and an emptied region is reseeded with a fresh `p` host (`local:seed-region`). The range is **clamped to a single region** (the start's, else the first swept; `local:clamped-range` also moves boundaries out of chrome) — one gesture, one region-keyed undo entry; other regions stay byte-identical. A **printable character replaces** the selection (the delete machine places the caret, `local:insert-text-at-caret` lands the character — same undo entry); Enter/Tab/paste are suppressed; plain arrows stay native. **Canonical copy/cut** (`ixsl:oncopy`/`oncut` on hosts and body): a cross-host selection is copied in its storage form — `cloneContents` into a carrier div, `mode="canonical"` + `cm:normalize` (the paste pipeline in reverse), so the clipboard's HTML flavor has no chrome/`contenteditable`/marker classes but keeps RDFa attributes; cut adds the delete machine (one undo entry); within-host copy stays native.
Expand Down
3 changes: 2 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,8 @@ <h4>Reordering</h4>
<h4>Seeing the result</h4>
<p><strong>Source</strong> (in the toolbar) shows the clean, canonical
XHTML+RDFa that would be saved. <strong>Extract RDF</strong> (top-right)
shows the facts a computer reads from this page.</p>
shows the facts a computer reads from this page. Both dialogs have a
<strong>Download</strong> button that saves the output as a file.</p>
</div>
</div>

Expand Down
21 changes: 21 additions & 0 deletions rdfa-editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,27 @@
color: #333;
}

.rdfa-editor-ui #output-download {
position: absolute;
right: 60px;
top: 22px;
padding: 5px 14px;
border: 1px solid #2196f3;
border-radius: 4px;
background: white;
color: #2196f3;
font-size: 13px;
font-weight: 500;
font-family: inherit;
cursor: pointer;
transition: background-color 0.2s, color 0.2s;
}

.rdfa-editor-ui #output-download:hover {
background: #2196f3;
color: white;
}

.rdfa-editor-ui .modal-content h3 {
margin-top: 0;
color: #333;
Expand Down
Loading
Loading