@elytracms/studio 0.0.83 → 0.0.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/builder/blocks-model.js +5 -2
- package/dist/components/builder/blocks-model.js.map +1 -1
- package/dist/components/builder/canvas-inline-editing.js +1 -1
- package/dist/components/builder/canvas-inline-editing.js.map +1 -1
- package/dist/components/builder/composition-field-editor.d.ts +48 -0
- package/dist/components/builder/composition-field-editor.js +94 -22
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/inline-edit.js +3 -2
- package/dist/components/builder/inline-edit.js.map +1 -1
- package/dist/components/builder/node-factory.d.ts +4 -0
- package/dist/components/builder/node-factory.js +14 -9
- package/dist/components/builder/node-factory.js.map +1 -1
- package/dist/components/cms/cms-composition-validators.d.ts +7 -16
- package/dist/components/cms/cms-composition-validators.js +101 -52
- package/dist/components/cms/cms-composition-validators.js.map +1 -1
- package/dist/components/cms/gate-validation.js +3 -1
- package/dist/components/cms/gate-validation.js.map +1 -1
- package/dist/components/cms/rich-text/component-embed.d.ts +13 -7
- package/dist/components/cms/rich-text/component-embed.js +21 -13
- package/dist/components/cms/rich-text/component-embed.js.map +1 -1
- package/dist/components/cms/rich-text/editor.d.ts +1 -1
- package/dist/components/cms/rich-text/editor.js +2 -2
- package/dist/components/cms/rich-text/editor.js.map +1 -1
- package/dist/components/cms/rich-text/extensions.d.ts +4 -0
- package/dist/components/cms/rich-text/extensions.js +6 -0
- package/dist/components/cms/rich-text/extensions.js.map +1 -1
- package/dist/components/cms/rich-text/insert.js +1 -1
- package/dist/components/cms/rich-text/insert.js.map +1 -1
- package/dist/components/cms/rich-text/unknown-node.d.ts +66 -0
- package/dist/components/cms/rich-text/unknown-node.js +101 -0
- package/dist/components/cms/rich-text/unknown-node.js.map +1 -0
- package/dist/components/cms/rich-text/value.d.ts +82 -13
- package/dist/components/cms/rich-text/value.js +169 -38
- package/dist/components/cms/rich-text/value.js.map +1 -1
- package/dist/components/cms/rich-text-preview.js +1 -1
- package/dist/components/cms/rich-text-preview.js.map +1 -1
- package/dist/lib/ai/rich-text-translate.js +3 -3
- package/dist/lib/ai/rich-text-translate.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +7 -3
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +4 -0
- package/dist/lib/i18n/messages.en.js +7 -3
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-translate.js","sourceRoot":"","sources":["../../../src/lib/ai/rich-text-translate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAQnF,qFAAqF;AAErF,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,MAAM,CAAC,IAAI,CAAA;AACnD,MAAM,SAAS,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,OAAO,CAAC,IAAI,CAAA;AACrD,MAAM,SAAS,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,MAAM,CAAC,IAAI,CAAA;AAEpD,mEAAmE;AACnE,MAAM,QAAQ,GAAG,2BAA2B,CAAA;AAE5C,uFAAuF;AACvF,SAAS,aAAa,CAAC,KAAa;IAClC,OAAO,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AACzC,CAAC;AAED,qFAAqF;AACrF,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAA;IACvF,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAA;IAC1E,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC;SAC7D,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;SAClC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAChD,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAC9D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAA;AACjC,CAAC;AA4BD,kEAAkE;AAClE,SAAS,UAAU,CAAC,IAAkB;IACpC,OAAO,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAA;AAC9D,CAAC;AAED,6FAA6F;AAC7F,SAAS,aAAa,CAAC,IAAkB;IACvC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;AACrE,CAAC;AAED,mFAAmF;AACnF,SAAS,WAAW,CAAC,IAAkB;IACrC,MAAM,UAAU,GAAqB,EAAE,CAAA;IACvC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC3C,MAAM,KAAK,GAAmB,EAAE,CAAA;IAChC,MAAM,QAAQ,GAAG,CAAC,KAAiC,EAAU,EAAE;QAC7D,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;QACtC,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACnC,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,QAAQ,CAAA;QAC3C,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAA;QAC7B,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QAC1D,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;QACvB,OAAO,GAAG,CAAA;IACZ,CAAC,CAAA;IACD,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACvC,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YAC/B,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,IAAI,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QAChE,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAA;YACtB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACjB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,CAAA;AACvD,CAAC;AAED,yFAAyF;AACzF,SAAS,mBAAmB,CAAC,OAAe;IAC1C,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAA;AACxD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC/C,MAAM,MAAM,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAA;IACtC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAA;IAC7C,MAAM,KAAK,GAAwB,EAAE,CAAA;IACrC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,CAAA;IAC9F,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAA;IAE1B,MAAM,IAAI,GAAG,CAAC,IAAkB,EAAE,IAAc,EAAQ,EAAE;QACxD,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW;YAAE,OAAM;QACrC,IAAI,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;YACxD,IAAI,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC;gBACjC,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;gBACzB,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAA;gBAC3C,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;YACnC,CAAC;YACD,OAAM;QACR,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IAChE,CAAC,CAAA;IAED,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACvD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAA;AAC3C,CAAC;AAED,qFAAqF;AAErF;;;;;;;GAOG;AACH,SAAS,WAAW,CAAC,UAAkB,EAAE,MAAmB;IAC1D,MAAM,GAAG,GAAmB,EAAE,CAAA;IAC9B,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAA;IACnC,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,KAAqB,EAAQ,EAAE;QAC7D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAM;QAC7B,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;IACnH,CAAC,CAAA;IACD,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC3C,IAAI,KAA6B,CAAA;IACjC,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9C,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,KAAK,CAAA;QAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAA;QAC5B,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;QACpD,gFAAgF;QAChF,yEAAyE;QACzE,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,IAAI,CAAA;QACzC,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YAChC,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAA;YAC9C,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YACpB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACd,MAAM,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAA;YACnC,SAAQ;QACV,CAAC;QACD,eAAe;QACf,IAAI,OAAO,KAAK,GAAG;YAAE,OAAO,IAAI,CAAA,CAAC,yCAAyC;QAC1E,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;QACtC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QACvB,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAA;QAC9B,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,CAAA;QACvD,IAAI,OAAO,KAAK,CAAC,CAAC;YAAE,OAAO,IAAI,CAAA;QAC/B,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;QACrD,2EAA2E;QAC3E,IAAI,aAAa,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QACrC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;QACtB,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC,MAAM,CAAA;QAC/B,EAAE,CAAC,SAAS,GAAG,MAAM,CAAA;IACvB,CAAC;IACD,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAC3D,+EAA+E;IAC/E,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IACvD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,uFAAuF;AACvF,SAAS,aAAa,CAAC,KAAqB,EAAE,IAAc,EAAE,OAAuB;IACnF,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAA;IAC5B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;QAC3B,IAAI,CAAC,KAAK,IAAI;YAAE,OAAO,IAAI,CAAA;QAC3B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAA;QAClD,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,CAAA;IAC/E,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAkB,EAClB,YAAyC;IAEzC,IAAI,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;IAClD,KAAK,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACvC,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACvC,IAAI,UAAU,KAAK,SAAS;YAAE,SAAQ;QACtC,MAAM,OAAO,GAAG,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;QAC/C,IAAI,CAAC,OAAO;YAAE,SAAQ;QACtB,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACxD,CAAC;IACD,MAAM,GAAG,GAAgB,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;IACjD,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAA;AACjC,CAAC;AAED,qFAAqF;AAErF;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe,EAAE,EAA+B;IACjF,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC3C,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,IAAI,KAA6B,CAAA;IACjC,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3C,GAAG,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;QACxD,MAAM,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;IACxC,CAAC;IACD,GAAG,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAA;IAChC,OAAO,GAAG,CAAA;AACZ,CAAC","sourcesContent":["/**\n * Structure-preserving rich-text translation (EC-332).\n *\n * The load-bearing invariant: **the model never emits node structure.** Each\n * translatable block's inline content is serialized to a single plain string in which\n * every text run is wrapped in an opaque placeholder token (`[[s{k}]]…[[/s{k}]]`) and\n * every non-text inline atom (image, embed, componentEmbed, hardBreak, mention, any\n * unknown inline node) is a self-closing token (`[[a{k}]]`). The model translates ONLY\n * the human-readable text between tokens; on reconstruction each token is mapped back to\n * the ORIGINAL stored mark stack / atom node — byte-for-byte — so marks, links, internal\n * `attrs.ref` references, and embeds survive by construction. Only `text` leaves change.\n *\n * This ports the Sanity precedent's `blockToMarkdown` / `markdownToChildren` moves to\n * Elytra's ProseMirror storage, but uses opaque placeholder tokens rather than literal\n * markdown: Elytra marks (`strike`/`underline`/`link` + its `attrs.ref` envelope) do not\n * round-trip through markdown losslessly, and reference-index integrity (EC-286) requires\n * the `ref` bytes to be preserved exactly — which token-indexed restoration guarantees.\n *\n * Robust against a misbehaving translator: if a translated block's tokens don't parse\n * back to a coherent structure, the ORIGINAL block is kept unchanged (never a broken\n * tree). Determinism: given a deterministic segment transform the output is identical\n * every run. See `rich-text-translate.test.ts` for the round-trip property tests.\n */\nimport { createRichTextValue, parseRichTextValue } from '@elytracms/core/rich-text'\nimport type {\n RichTextDoc,\n RichTextMark,\n RichTextNode,\n RichTextValue,\n} from '@elytracms/core/rich-text'\n\n// --- Token vocabulary -------------------------------------------------------------\n\nconst spanOpen = (k: number): string => `[[s${k}]]`\nconst spanClose = (k: number): string => `[[/s${k}]]`\nconst atomToken = (k: number): string => `[[a${k}]]`\n\n/** Matches every placeholder token: open/close spans and atoms. */\nconst TOKEN_RE = /\\[\\[(\\/?)([sa])(\\d+)\\]\\]/g\n\n/** True when a string contains any placeholder token (non-global; no shared state). */\nfunction containsToken(value: string): boolean {\n return /\\[\\[\\/?[sa]\\d+\\]\\]/.test(value)\n}\n\n/** Key-order-insensitive canonical JSON for de-duplicating identical mark stacks. */\nfunction canonicalJson(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null'\n if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`)\n return `{${entries.join(',')}}`\n}\n\n// --- Block-unit model -------------------------------------------------------------\n\n/** The per-block side tables the rebuild uses to restore marks + atoms verbatim. */\ninterface BlockTables {\n /** Path (content-index chain) to the text-bearing node within the doc. */\n path: number[]\n /** De-duplicated mark stacks; a span token's index selects one. */\n markStacks: RichTextMark[][]\n /** Inline atom nodes (image/embed/componentEmbed/hardBreak/…), preserved verbatim. */\n atoms: RichTextNode[]\n}\n\n/** One translatable rich-text block: an id stable within the field + its encoded text. */\nexport interface RichTextBlockUnit {\n /** Stable within the field (the block's content-index path, dot-joined). */\n id: string\n /** The encoded translatable string (text runs + placeholder tokens). */\n text: string\n}\n\n/** The extraction plan: the original value + every block's restoration tables. */\nexport interface RichTextPlan {\n value: RichTextValue\n blocks: Map<string, BlockTables>\n}\n\n/** True when a node is a text leaf (`{ type: 'text', text }`). */\nfunction isTextNode(node: RichTextNode): boolean {\n return node.type === 'text' && typeof node.text === 'string'\n}\n\n/** True when a node's direct children include a text leaf (⇒ it is an inline/text block). */\nfunction hasDirectText(node: RichTextNode): boolean {\n return Array.isArray(node.content) && node.content.some(isTextNode)\n}\n\n/** Encode one text-bearing block's inline content into a token string + tables. */\nfunction encodeBlock(node: RichTextNode): { encoded: string; markStacks: RichTextMark[][]; atoms: RichTextNode[] } {\n const markStacks: RichTextMark[][] = []\n const markIndex = new Map<string, number>()\n const atoms: RichTextNode[] = []\n const stackKey = (marks: RichTextMark[] | undefined): number => {\n const key = canonicalJson(marks ?? [])\n const existing = markIndex.get(key)\n if (existing !== undefined) return existing\n const idx = markStacks.length\n markStacks.push(marks ? marks.map((m) => ({ ...m })) : [])\n markIndex.set(key, idx)\n return idx\n }\n const parts: string[] = []\n for (const child of node.content ?? []) {\n if (isTextNode(child)) {\n const k = stackKey(child.marks)\n parts.push(`${spanOpen(k)}${child.text ?? ''}${spanClose(k)}`)\n } else {\n const k = atoms.length\n atoms.push(child)\n parts.push(atomToken(k))\n }\n }\n return { encoded: parts.join(''), markStacks, atoms }\n}\n\n/** True when an encoded block holds at least one non-whitespace translatable segment. */\nfunction hasTranslatableText(encoded: string): boolean {\n return encoded.replace(TOKEN_RE, '').trim().length > 0\n}\n\n/**\n * Walk a rich-text value and extract its translatable blocks. Deterministic; never\n * throws. A value that is not parseable rich text yields no units (and a plan whose\n * rebuild is a no-op). Code blocks are skipped (code is not prose).\n */\nexport function extractRichTextUnits(raw: unknown): { units: RichTextBlockUnit[]; plan: RichTextPlan } {\n const parsed = parseRichTextValue(raw)\n const blocks = new Map<string, BlockTables>()\n const units: RichTextBlockUnit[] = []\n if (!parsed.ok) {\n return { units, plan: { value: { version: 1, doc: { type: 'doc', content: [] } }, blocks } }\n }\n const value = parsed.value\n\n const walk = (node: RichTextNode, path: number[]): void => {\n if (node.type === 'codeBlock') return\n if (hasDirectText(node)) {\n const { encoded, markStacks, atoms } = encodeBlock(node)\n if (hasTranslatableText(encoded)) {\n const id = path.join('.')\n blocks.set(id, { path, markStacks, atoms })\n units.push({ id, text: encoded })\n }\n return\n }\n node.content?.forEach((child, i) => walk(child, [...path, i]))\n }\n\n value.doc.content.forEach((node, i) => walk(node, [i]))\n return { units, plan: { value, blocks } }\n}\n\n// --- Reconstruction ---------------------------------------------------------------\n\n/**\n * Parse an encoded (translated) block string back into inline nodes, or `null` on any\n * structural mismatch. Strict on purpose: the block is only rewritten when the translated\n * string is token-faithful — every atom token present exactly once, every span opened and\n * closed, and no non-whitespace text stranded outside a span (which would mean a dropped\n * formatting/reference token). Anything else keeps the ORIGINAL block, so an internal\n * link, image ref, or embed can never be silently lost to a garbled translation.\n */\nfunction decodeBlock(translated: string, tables: BlockTables): RichTextNode[] | null {\n const out: RichTextNode[] = []\n const atomsUsed = new Set<number>()\n let cursor = 0\n const pushText = (text: string, marks: RichTextMark[]): void => {\n if (text.length === 0) return\n out.push(marks.length > 0 ? { type: 'text', text, marks: marks.map((m) => ({ ...m })) } : { type: 'text', text })\n }\n const re = new RegExp(TOKEN_RE.source, 'g')\n let match: RegExpExecArray | null\n while ((match = re.exec(translated)) !== null) {\n const [token, closing, kind, digits] = match\n const index = Number(digits)\n const before = translated.slice(cursor, match.index)\n // Only whitespace may sit between/around tokens (a translator's added spacing);\n // stranded words mean a formatting/reference token was dropped → reject.\n if (before.trim().length > 0) return null\n if (kind === 'a') {\n const atom = tables.atoms[index]\n if (!atom || atomsUsed.has(index)) return null\n atomsUsed.add(index)\n out.push(atom)\n cursor = match.index + token.length\n continue\n }\n // kind === 's'\n if (closing === '/') return null // an unmatched close — garbled structure\n const marks = tables.markStacks[index]\n if (!marks) return null\n const close = spanClose(index)\n const closeAt = translated.indexOf(close, re.lastIndex)\n if (closeAt === -1) return null\n const inner = translated.slice(re.lastIndex, closeAt)\n // Inner must not itself contain tokens (spans never nest in our encoding).\n if (containsToken(inner)) return null\n pushText(inner, marks)\n cursor = closeAt + close.length\n re.lastIndex = cursor\n }\n if (translated.slice(cursor).trim().length > 0) return null\n // Every atom the block carried (image/embed/ref) must survive the translation.\n if (atomsUsed.size !== tables.atoms.length) return null\n return out\n}\n\n/** Deep-clone a node array and replace the inline content at `path` with `content`. */\nfunction replaceAtPath(nodes: RichTextNode[], path: number[], content: RichTextNode[]): RichTextNode[] {\n const [head, ...rest] = path\n return nodes.map((node, i) => {\n if (i !== head) return node\n if (rest.length === 0) return { ...node, content }\n return { ...node, content: replaceAtPath(node.content ?? [], rest, content) }\n })\n}\n\n/**\n * Rebuild a rich-text value from a plan + a per-block translation map. A block absent\n * from `translations` (skipped/aborted) or whose translated tokens don't parse keeps its\n * ORIGINAL content — never a broken structure. Returns a fresh `{ version, doc }` value.\n */\nexport function rebuildRichText(\n plan: RichTextPlan,\n translations: ReadonlyMap<string, string>,\n): RichTextValue {\n let content = plan.value.doc.content.map((n) => n)\n for (const [id, tables] of plan.blocks) {\n const translated = translations.get(id)\n if (translated === undefined) continue\n const decoded = decodeBlock(translated, tables)\n if (!decoded) continue\n content = replaceAtPath(content, tables.path, decoded)\n }\n const doc: RichTextDoc = { type: 'doc', content }\n return createRichTextValue(doc)\n}\n\n// --- Deterministic segment transform (FakeProvider + tests) -----------------------\n\n/**\n * Apply `fn` to only the human-readable text segments of an encoded block (the runs\n * between placeholder tokens), leaving every token exactly in place. This is how the\n * deterministic FakeProvider \"translates\" a block and how the property tests apply a\n * real text change without ever touching structure. Pure.\n */\nexport function mapEncodedSegments(encoded: string, fn: (segment: string) => string): string {\n const re = new RegExp(TOKEN_RE.source, 'g')\n let out = ''\n let cursor = 0\n let match: RegExpExecArray | null\n while ((match = re.exec(encoded)) !== null) {\n out += fn(encoded.slice(cursor, match.index)) + match[0]\n cursor = match.index + match[0].length\n }\n out += fn(encoded.slice(cursor))\n return out\n}\n"]}
|
|
1
|
+
{"version":3,"file":"rich-text-translate.js","sourceRoot":"","sources":["../../../src/lib/ai/rich-text-translate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Structure-preserving rich-text translation (EC-332).\n *\n * The load-bearing invariant: **the model never emits node structure.** Each\n * translatable block's inline content is serialized to a single plain string in which\n * every text run is wrapped in an opaque placeholder token (`[[s{k}]]…[[/s{k}]]`) and\n * every non-text inline atom (image, embed, componentEmbed, hardBreak, mention, any\n * unknown inline node) is a self-closing token (`[[a{k}]]`). The model translates ONLY\n * the human-readable text between tokens; on reconstruction each token is mapped back to\n * the ORIGINAL stored mark stack / atom node — byte-for-byte — so marks, links, internal\n * `attrs.ref` references, and embeds survive by construction. Only `text` leaves change.\n *\n * This ports the Sanity precedent's `blockToMarkdown` / `markdownToChildren` moves to\n * Elytra's ProseMirror storage, but uses opaque placeholder tokens rather than literal\n * markdown: Elytra marks (`strike`/`underline`/`link` + its `attrs.ref` envelope) do not\n * round-trip through markdown losslessly, and reference-index integrity (EC-286) requires\n * the `ref` bytes to be preserved exactly — which token-indexed restoration guarantees.\n *\n * Robust against a misbehaving translator: if a translated block's tokens don't parse\n * back to a coherent structure, the ORIGINAL block is kept unchanged (never a broken\n * tree). Determinism: given a deterministic segment transform the output is identical\n * every run. See `rich-text-translate.test.ts` for the round-trip property tests.\n */\nimport { createRichTextValue, parseRichTextValue } from '@elytracms/core/rich-text'\nimport type {\n RichTextDoc,\n RichTextMark,\n RichTextNode,\n RichTextValue,\n} from '@elytracms/core/rich-text'\n\n// --- Token vocabulary -------------------------------------------------------------\n\nconst spanOpen = (k: number): string => `[[s${k}]]`\nconst spanClose = (k: number): string => `[[/s${k}]]`\nconst atomToken = (k: number): string => `[[a${k}]]`\n\n/** Matches every placeholder token: open/close spans and atoms. */\nconst TOKEN_RE = /\\[\\[(\\/?)([sa])(\\d+)\\]\\]/g\n\n/** True when a string contains any placeholder token (non-global; no shared state). */\nfunction containsToken(value: string): boolean {\n return /\\[\\[\\/?[sa]\\d+\\]\\]/.test(value)\n}\n\n/** Key-order-insensitive canonical JSON for de-duplicating identical mark stacks. */\nfunction canonicalJson(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null'\n if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`)\n return `{${entries.join(',')}}`\n}\n\n// --- Block-unit model -------------------------------------------------------------\n\n/** The per-block side tables the rebuild uses to restore marks + atoms verbatim. */\ninterface BlockTables {\n /** Path (content-index chain) to the text-bearing node within the doc. */\n path: number[]\n /** De-duplicated mark stacks; a span token's index selects one. */\n markStacks: RichTextMark[][]\n /** Inline atom nodes (image/embed/componentEmbed/hardBreak/…), preserved verbatim. */\n atoms: RichTextNode[]\n}\n\n/** One translatable rich-text block: an id stable within the field + its encoded text. */\nexport interface RichTextBlockUnit {\n /** Stable within the field (the block's content-index path, dot-joined). */\n id: string\n /** The encoded translatable string (text runs + placeholder tokens). */\n text: string\n}\n\n/** The extraction plan: the original value + every block's restoration tables. */\nexport interface RichTextPlan {\n value: RichTextValue\n blocks: Map<string, BlockTables>\n}\n\n/** True when a node is a text leaf (`{ type: 'text', text }`). */\nfunction isTextNode(node: RichTextNode): boolean {\n return node.type === 'text' && typeof node.text === 'string'\n}\n\n/** True when a node's direct children include a text leaf (⇒ it is an inline/text block). */\nfunction hasDirectText(node: RichTextNode): boolean {\n return Array.isArray(node.content) && node.content.some(isTextNode)\n}\n\n/** Encode one text-bearing block's inline content into a token string + tables. */\nfunction encodeBlock(node: RichTextNode): { encoded: string; markStacks: RichTextMark[][]; atoms: RichTextNode[] } {\n const markStacks: RichTextMark[][] = []\n const markIndex = new Map<string, number>()\n const atoms: RichTextNode[] = []\n const stackKey = (marks: RichTextMark[] | undefined): number => {\n const key = canonicalJson(marks ?? [])\n const existing = markIndex.get(key)\n if (existing !== undefined) return existing\n const idx = markStacks.length\n markStacks.push(marks ? marks.map((m) => ({ ...m })) : [])\n markIndex.set(key, idx)\n return idx\n }\n const parts: string[] = []\n for (const child of node.content ?? []) {\n if (isTextNode(child)) {\n const k = stackKey(child.marks)\n parts.push(`${spanOpen(k)}${child.text ?? ''}${spanClose(k)}`)\n } else {\n const k = atoms.length\n atoms.push(child)\n parts.push(atomToken(k))\n }\n }\n return { encoded: parts.join(''), markStacks, atoms }\n}\n\n/** True when an encoded block holds at least one non-whitespace translatable segment. */\nfunction hasTranslatableText(encoded: string): boolean {\n return encoded.replace(TOKEN_RE, '').trim().length > 0\n}\n\n/**\n * Walk a rich-text value and extract its translatable blocks. Deterministic; never\n * throws. A value that is not parseable rich text yields no units (and a plan whose\n * rebuild is a no-op). Code blocks are skipped (code is not prose).\n */\nexport function extractRichTextUnits(raw: unknown): { units: RichTextBlockUnit[]; plan: RichTextPlan } {\n const parsed = parseRichTextValue(raw)\n const blocks = new Map<string, BlockTables>()\n const units: RichTextBlockUnit[] = []\n if (!parsed.ok) {\n return { units, plan: { value: { version: 2, content: [] }, blocks } }\n }\n const value = parsed.value\n\n const walk = (node: RichTextNode, path: number[]): void => {\n if (node.type === 'codeBlock') return\n if (hasDirectText(node)) {\n const { encoded, markStacks, atoms } = encodeBlock(node)\n if (hasTranslatableText(encoded)) {\n const id = path.join('.')\n blocks.set(id, { path, markStacks, atoms })\n units.push({ id, text: encoded })\n }\n return\n }\n node.content?.forEach((child, i) => walk(child, [...path, i]))\n }\n\n (value.content as RichTextNode[]).forEach((node, i) => walk(node, [i]))\n return { units, plan: { value, blocks } }\n}\n\n// --- Reconstruction ---------------------------------------------------------------\n\n/**\n * Parse an encoded (translated) block string back into inline nodes, or `null` on any\n * structural mismatch. Strict on purpose: the block is only rewritten when the translated\n * string is token-faithful — every atom token present exactly once, every span opened and\n * closed, and no non-whitespace text stranded outside a span (which would mean a dropped\n * formatting/reference token). Anything else keeps the ORIGINAL block, so an internal\n * link, image ref, or embed can never be silently lost to a garbled translation.\n */\nfunction decodeBlock(translated: string, tables: BlockTables): RichTextNode[] | null {\n const out: RichTextNode[] = []\n const atomsUsed = new Set<number>()\n let cursor = 0\n const pushText = (text: string, marks: RichTextMark[]): void => {\n if (text.length === 0) return\n out.push(marks.length > 0 ? { type: 'text', text, marks: marks.map((m) => ({ ...m })) } : { type: 'text', text })\n }\n const re = new RegExp(TOKEN_RE.source, 'g')\n let match: RegExpExecArray | null\n while ((match = re.exec(translated)) !== null) {\n const [token, closing, kind, digits] = match\n const index = Number(digits)\n const before = translated.slice(cursor, match.index)\n // Only whitespace may sit between/around tokens (a translator's added spacing);\n // stranded words mean a formatting/reference token was dropped → reject.\n if (before.trim().length > 0) return null\n if (kind === 'a') {\n const atom = tables.atoms[index]\n if (!atom || atomsUsed.has(index)) return null\n atomsUsed.add(index)\n out.push(atom)\n cursor = match.index + token.length\n continue\n }\n // kind === 's'\n if (closing === '/') return null // an unmatched close — garbled structure\n const marks = tables.markStacks[index]\n if (!marks) return null\n const close = spanClose(index)\n const closeAt = translated.indexOf(close, re.lastIndex)\n if (closeAt === -1) return null\n const inner = translated.slice(re.lastIndex, closeAt)\n // Inner must not itself contain tokens (spans never nest in our encoding).\n if (containsToken(inner)) return null\n pushText(inner, marks)\n cursor = closeAt + close.length\n re.lastIndex = cursor\n }\n if (translated.slice(cursor).trim().length > 0) return null\n // Every atom the block carried (image/embed/ref) must survive the translation.\n if (atomsUsed.size !== tables.atoms.length) return null\n return out\n}\n\n/** Deep-clone a node array and replace the inline content at `path` with `content`. */\nfunction replaceAtPath(nodes: RichTextNode[], path: number[], content: RichTextNode[]): RichTextNode[] {\n const [head, ...rest] = path\n return nodes.map((node, i) => {\n if (i !== head) return node\n if (rest.length === 0) return { ...node, content }\n return { ...node, content: replaceAtPath(node.content ?? [], rest, content) }\n })\n}\n\n/**\n * Rebuild a rich-text value from a plan + a per-block translation map. A block absent\n * from `translations` (skipped/aborted) or whose translated tokens don't parse keeps its\n * ORIGINAL content — never a broken structure. Returns a fresh `{ version, doc }` value.\n */\nexport function rebuildRichText(\n plan: RichTextPlan,\n translations: ReadonlyMap<string, string>,\n): RichTextValue {\n let content = (plan.value.content as RichTextNode[]).map((n) => n)\n for (const [id, tables] of plan.blocks) {\n const translated = translations.get(id)\n if (translated === undefined) continue\n const decoded = decodeBlock(translated, tables)\n if (!decoded) continue\n content = replaceAtPath(content, tables.path, decoded)\n }\n const doc: RichTextDoc = { type: 'doc', content }\n return createRichTextValue(doc)\n}\n\n// --- Deterministic segment transform (FakeProvider + tests) -----------------------\n\n/**\n * Apply `fn` to only the human-readable text segments of an encoded block (the runs\n * between placeholder tokens), leaving every token exactly in place. This is how the\n * deterministic FakeProvider \"translates\" a block and how the property tests apply a\n * real text change without ever touching structure. Pure.\n */\nexport function mapEncodedSegments(encoded: string, fn: (segment: string) => string): string {\n const re = new RegExp(TOKEN_RE.source, 'g')\n let out = ''\n let cursor = 0\n let match: RegExpExecArray | null\n while ((match = re.exec(encoded)) !== null) {\n out += fn(encoded.slice(cursor, match.index)) + match[0]\n cursor = match.index + match[0].length\n }\n out += fn(encoded.slice(cursor))\n return out\n}\n"]}
|
|
@@ -532,9 +532,12 @@ export const deMessages = {
|
|
|
532
532
|
'cms.issueMessage.requiredFieldMissing': 'Das Pflichtfeld „{field}“ fehlt.',
|
|
533
533
|
'cms.issueMessage.requiredPropMissing': 'Die Pflichteigenschaft „{field}“ fehlt.',
|
|
534
534
|
'cms.issueMessage.perLocaleMapValue': 'Das Feld „{field}“ erwartet einen Wert vom Typ „{type}“, enthält aber eine Sprach-Zuordnung (Schlüssel: {keys}) — sprachabhängige Werte liegen im „localized“-Bereich des Dokuments, niemals im Feldwert selbst.',
|
|
535
|
-
'cms.issueMessage.
|
|
536
|
-
'cms.issueMessage.
|
|
537
|
-
'cms.issueMessage.
|
|
535
|
+
'cms.issueMessage.preConvergenceV1': 'Das Feld „{field}“ enthält eine Inhaltsform von vor der Konvergenz (die v1-Hülle „{ version, doc }“) — die Engine liest nur noch die konvergierte Sequenz „{ version: 2, content }“. Dieser Inhalt muss neu eingespielt werden (Re-Seed).',
|
|
536
|
+
'cms.issueMessage.preConvergenceBareBlocks': 'Das Feld „{field}“ enthält eine Inhaltsform von vor der Konvergenz (eine blanke Komponentenliste) — die Engine liest nur noch die konvergierte Sequenz „{ version: 2, content }“. Dieser Inhalt muss neu eingespielt werden (Re-Seed).',
|
|
537
|
+
'cms.issueMessage.preConvergencePlaintext': 'Das Feld „{field}“ enthält eine Inhaltsform von vor der Konvergenz (unstrukturierter Alttext) — die Engine liest nur noch die konvergierte Sequenz „{ version: 2, content }“. Dieser Inhalt muss neu eingespielt werden (Re-Seed).',
|
|
538
|
+
'cms.issueMessage.richTextHoldsArray': 'Das Rich-Text-Feld „{field}“ erwartet einen Rich-Text-Wert („{ version: 2, content }“), enthält aber ein Array.',
|
|
539
|
+
'cms.issueMessage.richTextHoldsBareDoc': 'Das Rich-Text-Feld „{field}“ erwartet einen Rich-Text-Wert („{ version: 2, content }“), enthält aber ein blankes Rich-Text-Dokument ohne die Hülle „{ version: 2, content }“.',
|
|
540
|
+
'cms.issueMessage.richTextHoldsObject': 'Das Rich-Text-Feld „{field}“ erwartet einen Rich-Text-Wert („{ version: 2, content }“), enthält aber ein Objekt mit den Schlüsseln „{keys}“.',
|
|
538
541
|
'cms.issueMessage.unknownCollectionForDocument': 'Das Dokument „{id}“ verweist auf die unbekannte Sammlung „{collection}“.',
|
|
539
542
|
'cms.issueMessage.localizedPlainValue': 'Das sprachabhängige Feld „{field}“ enthält einen einfachen Wert im gemeinsamen „values“-Bereich — erwartet werden sprachabhängige Werte unter „localized.<locale>.{field}“. Der einfache Wert dient nur noch als letzte Rückfallebene.',
|
|
540
543
|
'cms.issueMessage.requiredLocalizedMissingDefault': 'Das sprachabhängige Pflichtfeld „{field}“ fehlt für die Standardsprache „{locale}“.',
|
|
@@ -1208,6 +1211,7 @@ export const deMessages = {
|
|
|
1208
1211
|
'richText.embed.editTitle': '{name} bearbeiten',
|
|
1209
1212
|
'richText.embed.noProps': 'Diese Komponente hat keine bearbeitbaren Einstellungen.',
|
|
1210
1213
|
'richText.embed.blocksNote': 'Verschachtelte Inhaltsbereiche werden auf der Arbeitsfläche bearbeitet, nicht hier.',
|
|
1214
|
+
'richText.carrier.unknown': 'Unbekannter Inhaltsblock:',
|
|
1211
1215
|
'richText.slash.text': 'Text',
|
|
1212
1216
|
'richText.slash.empty': 'Keine passenden Blöcke',
|
|
1213
1217
|
'richText.slash.componentsGroup': 'Komponenten',
|