8ae010218e
10 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 79376bcdd1 |
Give wikilinks a relative href in static/public HTML
The content-cache renderer (docToHtml) emitted wikilinks without an href, so on the read-only public page view they rendered as styled but unclickable text. Emit href="<slug>" — relative to the current page URL, it resolves to the sibling page under both /public/<pond>/… and the in-app /p/<pond>/… version-history preview, without the renderer needing pond context. Slugs are [a-z0-9-], safe as a bare path segment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 9e8ebfe49c |
Degrade plugin content gracefully in HTML, PDF, and office exports (#79)
All checks were successful
CI / Lint, typecheck, test (push) Successful in 2m57s
CD / Promote to Int (push) Successful in 9s
CI / Auth e2e pack (push) Successful in 5m2s
CI / Import/export fidelity gate (push) Successful in 46s
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m10s
CD / Deploy to Test (push) Successful in 14s
CD / Smoke tests against Test (push) Successful in 1m12s
Completes M7: exports and the public read view no longer show raw plugin placeholders (ADR 0008/0009). - PluginFallbackRenderer (api): replaces each plugin-block placeholder in content-cache HTML with its best static form — the block's stored SVG snapshot (block data is author-controlled, so it passes the same DOMPurify sanitizer as uploaded SVG files before entering host HTML), else the manifest fallback from the stored snapshot (text, or an image inlined as a data URI so network-isolated renderers work; tombstone-safe for uninstalled plugins), else the literal '[plugin content]' marker. - Office exports (docx/odt): the export markdown is degraded before pandoc — GFM knows neither the dorfteich-plugin fence nor the section fenced div, so blocks become their fallback text and sections plain quoted blocks (shared replacePluginNodesForExport, AST-level so nesting and embedded blocks inside sections survive). - PDF export applies the HTML fallback pass before building the Gotenberg document — resolving the TODO left in #67. - Public read view: the same fallback pass plus the pond's active section-style CSS inlined as a <style> block, so public pages show styled sections and static plugin content without any plugin runtime. - Covered in export.service.db.test (snapshot SVG sanitized — hostile <script> stripped; manifest text; tombstone text; quoted sections and no fence artifacts in the pandoc input) and shared export-fallbacks tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 923532f5f7 |
Add block plugins: plugin_block node with sandboxed rendering and editing (#76)
All checks were successful
CD / Promote to Int (push) Successful in 9s
CI / Auth e2e pack (push) Successful in 3m56s
CI / Import/export fidelity gate (push) Successful in 43s
CI / Lint, typecheck, test (push) Successful in 2m53s
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m9s
CD / Smoke tests against Test (push) Successful in 1m9s
CD / Deploy to Test (push) Successful in 12s
The powerful end of the plugin spectrum (ADR 0008 extension point `block`): - Shared schema: the reserved `plugin_block` node — a block atom carrying pluginId, blockType, and the block data as a JSON object. Its DOM shape round-trips the full state in data attributes (clipboard-safe), markdown maps to a reserved fence (```dorfteich-plugin <plugin>/<type> + data JSON body, fence-escalated when the payload contains backticks), and the content-cache HTML renders a data-carrying neutral placeholder until the export fallbacks land (#79). - Editor: a React NodeView hosts the #73 sandbox — render lifecycle on mount, an edit affordance switching the frame to the plugin's edit mode, and the blockData capability persisting through node attrs (a normal editor transaction, so Yjs replicates it; writes are refused on read-only editors, and the plugin's own attr echo is suppressed so its edit UI never resets mid-typing). Collaborator changes re-invoke the current lifecycle, keeping frames live. The page surface (ids, openPage) flows through a React context like the wikilink pattern; the toolbar gets an insert picker fed from the active code plugins' block extension points. - Fallback: GET /plugins/:id/fallback resolves the manifest fallback from the stored snapshot — it survives uninstall as a tombstone, image fallbacks degrade to neutral once assets are gone. Signed-in only. - e2e plugin-blocks.spec.ts covers all four acceptance criteria: insert → edit → reload round-trip, live two-user collab, disable → fallback → re-enable without document mutation, and copy/paste within and across pages (the markdown clipboard carries the reserved fence). getBlock (cross-page block embedding) stays deferred as in #74: the schema has no per-block ids yet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 2e96173d44 |
Add the section container node to the document model (#75, part 1)
Some checks failed
CD / Build and push images (push) Successful in 3m15s
CD / Deploy to Test (push) Successful in 9s
CI / Import/export fidelity gate (push) Successful in 47s
CI / Auth e2e pack (push) Successful in 4m12s
CD / Smoke tests against Test (push) Successful in 1m4s
CD / Promote to Int (push) Successful in 10s
CI / Build container images (push) Has been skipped
CI / Warm action cache (push) Successful in 10s
CI / Lint, typecheck, test (push) Failing after 1m27s
Document-model foundation for section-style plugins (ADR 0008 kind
section_style): a `section` block node carrying `pluginId` + `styleId`,
rendered as `<div class="dt-section dt-style-<pluginId>-<styleId>">` so a
plugin's sanitized, scoped CSS applies and — when the plugin is gone —
the class matches nothing and the content stays intact with neutral
styling. Round-trips to a Pandoc-style fenced div
(`::: {data-section-style="<pluginId>/<styleId>"}`), with a markdown-it
block rule that supports nesting.
This is the schema/markdown/HTML half of #75; the editor UI (wrap/style
picker/unwrap), the `section-styles-basic` reference plugin, per-pond CSS
injection, and PDF/office export styling follow in part 2. The node is
additive and backward-compatible (existing documents have none).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
|
|||
| 14e69b399c |
Add wikilink index, backlinks API, and phantom resolution (#47)
All checks were successful
CD / Build and push images (push) Successful in 3m2s
CI / Lint, typecheck, test (push) Successful in 2m16s
CI / Auth e2e pack (push) Successful in 2m44s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 11s
Maintain a server-side `page_links` index on every content change so
backlinks and missing-target ("phantom") links can be queried.
- prisma: `PageLink` (from_page_id, nullable to_page_id, target_slug;
unique per (from, slug); cascade on source purge, set-null on target
purge); migration.
- shared: `extractWikilinkSlugs(doc)` (distinct target slugs) and the
`BacklinkView` / `PhantomLinkView` read shapes.
- collab: the persistence hook (#35) now rewrites the source page's outgoing
links in the same transaction as the content cache — one row per distinct
wikilink slug, resolved to a page in the same pond (null = phantom).
- api: `GET /pages/:id/backlinks` (permission-filtered — wikilinks resolve
within a pond, so seeing the pond is the read right) and
`GET /ponds/:id/phantom-links` (missing targets grouped with their
referrers). Creating or renaming a page to a slug that pages already link
to resolves those phantom rows; because links store the target's id,
backlinks survive a later rename of the target's slug.
- tests: shared extraction unit test; collab persistence db test (store
writes resolved + phantom rows and rewrites the index); api LinksService
db test (backlinks, permission filter, phantom aggregation, create/rename
resolution, id-based backlinks survive target rename).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
|
|||
| 7244b89215 |
Add wikilink node with autocomplete (#46)
All checks were successful
CD / Build and push images (push) Successful in 3m2s
CI / Lint, typecheck, test (push) Successful in 2m15s
CI / Auth e2e pack (push) Successful in 2m42s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 11s
Introduce Obsidian-style `[[page links]]` (ADR 0004).
- shared: reserved `wikilink` inline atom in the editor schema (attrs
`targetSlug`, optional `displayText`); markdown mapping `[[slug]]` /
`[[slug|text]]` via a markdown-it inline rule + serializer node; plain-text
and HTML derivation include the shown text. Round-trip + parse unit tests.
- web:
- `Wikilink` node extension with a React NodeView: shows the explicit
display text or the target's current title (so a rename updates the link),
renders a missing target as a dashed phantom with a tooltip, navigates on
click in read mode.
- `[[` autocomplete popup (`WikilinkAutocomplete`), dependency-free: filters
the pond's pages as you type with a create-new-page hint for misses,
Enter/click inserts the node and removes the typed `[[query`; ↑/↓/Enter/Esc
intercepted in the capture phase so ProseMirror does not act on them.
- `WikilinkContext` provides the pond's pages (slug→title) for live
resolution and the autocomplete, populated by the page editor.
- i18n `editor.wikilink.*` (de + en); wikilink + phantom + popup styles.
- e2e `wikilink.spec.ts` (new CI pack): type `[[`, autocomplete filters and
inserts a working link that resolves the target title and persists across a
reload. Phantom → live resolution on page creation is verified in #47.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
|
|||
| b5cc4c34b8 |
Add link UX: edit URL and open in new tab (#29)
All checks were successful
CD / Build and push images (push) Successful in 2m2s
CI / Lint, typecheck, test (push) Successful in 1m41s
CI / Auth e2e pack (push) Successful in 1m49s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m9s
CD / Promote to Int (push) Successful in 9s
A bubble menu on link selection offers "edit URL", "open in new tab", and "remove link"; Mod-k opens the same editor for the current selection (creating a link if there isn't one yet), and the toolbar button does the same. Invalid protocols (e.g. javascript:) show a localized inline error instead of silently no-oping. Pasting a URL over selected text links it instead of replacing the text. Links always render with target="_blank" so read mode opens them in a new tab by default; edit mode suppresses the resulting navigate-on- click (Mod-click still follows it), since a plain click there should place the cursor instead. Closes #29 |
|||
| c8be3cd85e |
Add image paste and insert in the editor (#28)
All checks were successful
CD / Build and push images (push) Successful in 2m3s
CI / Lint, typecheck, test (push) Successful in 1m39s
CI / Auth e2e pack (push) Successful in 1m48s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m9s
CD / Promote to Int (push) Successful in 10s
Paste and drag-and-drop of image files upload via the #27 API and insert a real image node only once the upload succeeds; the in-flight state is a ProseMirror decoration, not a document node, so a failed upload cannot leave anything broken behind (it shows a transient inline error instead). The toolbar's image button opens a native file picker into the same upload path. Selecting an image reveals inline alt-text and width-preset (small/medium/full) controls. Also fixes the image node's parseDOM, which had no getAttrs and would drop the required fileId attribute on internal copy/paste. Closes #28 |
|||
| b0d9a00c18 |
Fix Prettier formatting in editor-schema (#24)
All checks were successful
CD / Build and push images (push) Successful in 1m50s
CI / Lint, typecheck, test (push) Successful in 1m23s
CI / Auth e2e pack (push) Successful in 1m42s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m4s
CD / Promote to Int (push) Successful in 10s
The #24 commit passed ESLint but not the repo's Prettier check (pnpm lint runs both) — CI caught it after the push. Formatting only, no behavior change. |
|||
| b89aa6bed0 |
Add editor document schema in packages/shared (#24)
Some checks failed
CD / Build and push images (push) Successful in 1m50s
CI / Lint, typecheck, test (push) Failing after 52s
CI / Auth e2e pack (push) Successful in 1m47s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m5s
CD / Promote to Int (push) Successful in 10s
ProseMirror schema (headings 1-4, lists incl. task lists, blockquote, code block, tables via prosemirror-tables, images, hard breaks; bold/ italic/code/strikethrough/link marks) plus docToMarkdown, markdownToDoc, docToPlainText, docToHtml, and extractOutline built on it. Markdown parsing extends markdown-it's default preset with a token-stream transform for GFM task lists and table-cell paragraph wrapping. docToHtml hand-rolls escaping and link-protocol allowlisting with zero DOM dependencies, so it runs in the API/collab server as well as the browser. Node names `wikilink` and `plugin_block` are reserved for later stories. Closes #24 |