@docx-editor.dev/pro 2.0.1 → 2.1.0
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/README.md +6 -6
- package/dist/chunk-FVI3MGO7.js +1 -0
- package/dist/chunk-MNK6DXJQ.cjs +1 -0
- package/dist/define-custom-node-BTTX66B3.d.cts +500 -0
- package/dist/define-custom-node-BTTX66B3.d.ts +500 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +323 -36
- package/dist/index.d.ts +323 -36
- package/dist/index.js +1 -1
- package/dist/react/index.cjs +2 -2
- package/dist/react/index.d.cts +31 -16
- package/dist/react/index.d.ts +31 -16
- package/dist/react/index.js +2 -2
- package/package.json +3 -2
- package/dist/chunk-NZOS3H23.js +0 -1
- package/dist/chunk-TNSHSAA4.cjs +0 -1
- package/dist/define-custom-node-CkkDPdB0.d.cts +0 -191
- package/dist/define-custom-node-CkkDPdB0.d.ts +0 -191
package/README.md
CHANGED
|
@@ -60,8 +60,8 @@ UI; the module is what makes them visible and actionable.
|
|
|
60
60
|
|
|
61
61
|
## Chrome or hooks
|
|
62
62
|
|
|
63
|
-
Everything the packaged sidebar renders is reachable from `useReview()`.
|
|
64
|
-
Word-like cards out of the box
|
|
63
|
+
Everything the packaged sidebar renders is reachable from `useReview()`. Use the sidebar for
|
|
64
|
+
Word-like cards out of the box, or the hook to render your own markup.
|
|
65
65
|
|
|
66
66
|
```tsx
|
|
67
67
|
import { useReview } from '@docx-editor.dev/pro/react';
|
|
@@ -95,7 +95,7 @@ repaint behind the page or break during pagination.
|
|
|
95
95
|
|
|
96
96
|
## Custom nodes
|
|
97
97
|
|
|
98
|
-
An inline node type you define
|
|
98
|
+
An inline node type you define (a citation, a mention, a merge field) stored as a Word content
|
|
99
99
|
control whose `w:tag` carries your identity and attributes. Word opens the document, shows the
|
|
100
100
|
node's text, and gives it back unchanged.
|
|
101
101
|
|
|
@@ -120,10 +120,10 @@ Every value reaching `fromDocx` came out of a `.docx`, so treat `attrs` and `tex
|
|
|
120
120
|
|
|
121
121
|
## Licensing
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
Unlike the editor packages, this one is not Apache 2.0. It is licensed under the
|
|
124
124
|
[EigenPal Pro Evaluation License 1.0](https://github.com/eigenpal/docx-editor/blob/main/packages/pro/LICENSE.md):
|
|
125
|
-
free to read, run, and modify internally to evaluate. Production use
|
|
126
|
-
environment, business-operational data, or this package inside something you offer to others
|
|
125
|
+
free to read, run, and modify internally to evaluate. Production use (a live or customer-facing
|
|
126
|
+
environment, business-operational data, or this package inside something you offer to others)
|
|
127
127
|
requires a written commercial agreement, and so does redistribution.
|
|
128
128
|
|
|
129
129
|
Commercial licensing: [licensing@eigenpal.com](mailto:licensing@eigenpal.com)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {contentControlsIn,contentControlPropertiesOf,contentControlTextOf,findNode,revisionItemsOf,collectReviewItems,paragraphOrderOfPart,parseNoteScopeId,locateSites,customNodePayloadsOf,customNodePayloadsByControl,segmentsOf}from'@docx-editor.dev/core/store';import {reviewItemPositionRank}from'@docx-editor.dev/core/layout';function g(e){}var Oe=64,H=new Set(["__proto__","constructor","prototype"]);function h(e,t,o){let n=new URLSearchParams;for(let[s,d]of Object.entries(o))n.set(s,d);let r=n.toString(),a=r.length>0?`${e}:${t}?${r}`:`${e}:${t}`;return a.length>64?{ok:false,reason:"tag-overflow",length:a.length}:{ok:true,tag:a}}function N(e){if(e.length>64)return null;let t=e.indexOf(":");if(t<=0)return null;let o=e.slice(0,t),n=e.slice(t+1),r=n.indexOf("?"),a=r===-1?n:n.slice(0,r);if(a.length===0)return null;let s=Object.create(null);if(r!==-1)for(let[d,i]of new URLSearchParams(n.slice(r+1))){if(H.has(d))return null;s[d]=i;}return {prefix:o,name:a,attrs:s}}var Ie=262144;function x(e,t){if(t.length===0)return {ok:false,reason:"malformed",issues:["the node carries no payload"]};if(t.length>262144)return {ok:false,reason:"malformed",issues:[`the payload is ${String(t.length)} characters; the cap is ${String(262144)}`]};let o;try{o=JSON.parse(t);}catch(a){return {ok:false,reason:"malformed",issues:[a instanceof Error?a.message:"the payload is not JSON"]}}let n=I(o);if(!e)return {ok:true,value:n};let r=e["~standard"].validate(n);return B(r)?{ok:false,reason:"async",issues:["the schema validates asynchronously, which the read path cannot await"]}:r.issues?{ok:false,reason:"invalid",issues:r.issues.map(q)}:{ok:true,value:r.value}}function R(e){let t;try{t=JSON.stringify(e)??"";}catch(o){return {ok:false,reason:"malformed",issues:[o instanceof Error?o.message:"the value cannot be serialized"]}}return t.length===0?{ok:false,reason:"malformed",issues:["the value serializes to nothing"]}:t.length>262144?{ok:false,reason:"malformed",issues:[`the payload is ${String(t.length)} characters; the cap is ${String(262144)}`]}:{ok:true,value:t}}function B(e){return typeof e.then=="function"}function q(e){let t=(e.path??[]).map(o=>String(typeof o=="object"?o.key:o)).join(".");return t.length>0?`${t}: ${e.message}`:e.message}var Y=new Set(["__proto__","constructor","prototype"]);function I(e){if(Array.isArray(e))return e.map(I);if(e===null||typeof e!="object")return e;let t=Object.create(null);for(let[o,n]of Object.entries(e))Y.has(o)||(t[o]=I(n));return t}function _(e){let t=(e.path??[]).map(o=>{let n=typeof o=="object"?o.key:o;return typeof n=="number"?n:String(n)});return {message:e.message,path:t,pointer:t.join(".")}}function S(e,t){return {ok:false,code:"invalidArgs",reason:e,issues:t}}var b="docxEditor";function v(e){return e.payloadNamespace??`urn:docx-editor.dev:custom-node:${e.tagPrefix}`}function M(e,t,o){let n=0;for(let r of customNodePayloadsOf(e,t,o).keys()){let a=/^cx(\d{1,9})$/.exec(r);if(!a)continue;let s=Number(a[1]);s>n&&(n=s);}return `cx${String(n+1)}`}function L(e,t){let o=R(t);if(!o.ok)return {ok:false,reason:`the payload cannot be serialized: ${o.issues.join(", ")}`,issues:[]};if(!e.schema)return {ok:true,data:o.value,value:t};let r=e.schema["~standard"].validate(JSON.parse(o.value));if(Q(r))return {ok:false,reason:`${e.name}'s schema validates asynchronously, which a write cannot await`,issues:[]};if(r.issues){let s=r.issues.map(_);return {ok:false,reason:`the payload does not match ${e.name}'s schema: `+s.map(d=>(d.pointer?`${d.pointer}: `:"")+d.message).join(", "),issues:s}}let a=R(r.value);return a.ok?{ok:true,data:a.value,value:r.value}:{ok:false,reason:`the payload cannot be serialized: ${a.issues.join(", ")}`,issues:[]}}function Q(e){return typeof e.then=="function"}function z(e){return typeof e=="object"&&e!==null&&typeof e.name=="string"&&typeof e.tagPrefix=="string"}var V=/^[A-Za-z0-9_.-]+$/,te=/['"<>&]|[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/;function oe(e){return e.length>0&&!te.test(e)}function Ve(e){if(!V.test(e.name??""))throw new Error(`defineCustomNode: invalid name ${JSON.stringify(e.name)}`);if(!V.test(e.tagPrefix??""))throw new Error(`defineCustomNode: invalid tagPrefix ${JSON.stringify(e.tagPrefix)}`);let t=e.schema;if(t!==void 0&&typeof t?.["~standard"]?.validate!="function")throw new Error(`defineCustomNode: ${JSON.stringify(e.name)} has a schema that does not implement Standard Schema`);if(e.payloadNamespace!==void 0&&!oe(e.payloadNamespace))throw new Error(`defineCustomNode: ${JSON.stringify(e.name)} has a payloadNamespace that cannot be written into an XPath \u2014 no quotes, angle brackets, ampersands or control characters: ${JSON.stringify(e.payloadNamespace)}`);if(e.preserveOnExport!==void 0&&e.preserveOnExport!==true&&e.preserveOnExport!==false&&e.preserveOnExport!=="text")throw new Error(`defineCustomNode: ${JSON.stringify(e.name)} has an unknown preserveOnExport ${JSON.stringify(e.preserveOnExport)}`);return Object.freeze({...e,dataOf:n=>{if(!n||n.data===void 0||n.name!==void 0&&n.name!==e.name)return;if(!e.schema)return n.data;let r=e.schema["~standard"].validate(n.data);if(typeof r.then=="function")return;let a=r;return a.issues?void 0:a.value}})}function ze(e){g(e.licenseKey);let t=new Set;for(let o of e.nodes){let n=`${o.tagPrefix}:${o.name}`;if(t.has(n))throw new Error(`customNodesModule: two definitions claim ${JSON.stringify(n)}`);t.add(n);}return {id:"custom-nodes",customNodes:e.nodes,...e.onDiagnostic?{onCustomNodeDiagnostic:o=>{e.onDiagnostic?.(o);}}:{},customNodePayloadNamespaces:[...new Set(e.nodes.map(v))]}}function ne(e){for(let t of e.children)if(!(t.kind==="textValue"||t.localName!=="sdtPr")){for(let o of t.children)if(!(o.kind==="textValue"||o.localName!=="tag")){for(let n of o.attributes)if(n.localName==="val")return n.value}}}function j(e){if(e.kind==="textValue")return e.value;let t="";for(let o of e.children)t+=j(o);return t}function W(e,t,o={}){if(t.length===0)return [];let n=new Map;for(let s of t){let d=`${s.tagPrefix}:${s.name}`;if(n.has(d))throw new Error(`recognizeCustomNodes: duplicate definition for ${JSON.stringify(d)}`);n.set(d,s);}let r=[],a=(s,d)=>{if(!(s.kind==="textValue"||d>64)){if(s.kind==="contentControl"){let i=ne(s),c=i!==void 0?N(i):null,u=c?n.get(`${c.prefix}:${c.name}`):void 0;if(c&&u&&i!==void 0){let l=j(s),f=re(u,s.id,o.payloads?.get(s.id),contentControlPropertiesOf(s).dataBinding!==void 0,o.onDiagnostic),m=u.fromDocx?u.fromDocx({attrs:c.attrs,text:l,...f.present?{data:f.value}:{}}):c.attrs;if(m!==null){r.push({name:u.name,attrs:m,text:l,nodeId:s.id,tag:i,...f.present?{data:f.value}:{}});return}}}for(let i of s.children)a(i,d+1);}};return a(e.root,0),r}function re(e,t,o,n,r){if(!o)return n&&r?.({code:"payload-missing",name:e.name,nodeId:t,issues:["the control binds a store node this document does not hold"]}),{present:false};let a=x(e.schema,o.data);return a.ok?{present:true,value:a.value}:(r?.({code:"payload-invalid",name:e.name,nodeId:t,issues:a.issues}),{present:false})}function K(e,t){let o=findNode(e,t);return !o||o.kind!=="paragraph"?[]:revisionItemsOf({id:e.id,name:e.name,contentType:e.contentType,root:o})}function ce(e,t,o,n){if(t.length===0)return [];let r=W(e,t,{...o===void 0?{}:{payloads:o},...n===void 0?{}:{onDiagnostic:n}});if(r.length===0)return [];let a=locateSites(e),s=[];for(let d of r){let i=t.find(l=>l.name===d.name&&d.tag.startsWith(`${l.tagPrefix}:`));if(!i)continue;let c=i.reviewCard?i.reviewCard({attrs:d.attrs,text:d.text,...d.data===void 0?{}:{data:d.data}}):null;if(i.reviewCard&&c===null)continue;let u=a.get(d.nodeId);s.push({kind:"custom",id:d.nodeId,name:d.name,tag:d.tag,attrs:d.attrs,text:d.text,...d.data===void 0?{}:{data:d.data},carded:i.reviewCard!==void 0&&c!==null,title:c?.title??"",...c?.detail!==void 0?{detail:c.detail}:{},range:u?{partName:e.name,start:{paragraphId:u.paragraphId,offset:u.start},end:{paragraphId:u.paragraphId,offset:u.end}}:null});}return s}function U(e){let t=collectReviewItems(e),o=(e.customNodes??[]).filter(z);if(o.length===0)return t;let n=[e.storyPart],r=new Set([e.storyPart.name]);for(let i of e.furnitureParts??[])r.has(i.name)||(r.add(i.name),n.push(i));let a=[],s=new Map;for(let i of n){a.push(...ce(i,o,i.name===e.storyPart.name?e.customNodePayloads:void 0,e.reportCustomNodeDiagnostic));let c=s.size;for(let[u,l]of paragraphOrderOfPart(i))s.has(u)||s.set(u,c+l);}return a.length===0?t:[...t,...a].sort((i,c)=>reviewItemPositionRank(i,s)-reviewItemPositionRank(c,s))}function ot(e={}){return g(e.licenseKey),{id:"review",review:{displayModes:["all-markup","proposed","original"],collectReviewItems:U,revisionItemsOfParagraph:K}}}function G(e){return e.surface??null}function E(e,t){if(t==null)return null;let o=L(e,t);return o.ok?{ok:true,serialized:o.data,value:o.value}:{ok:false,reason:o.reason,issues:o.issues}}function D(e,t,o,n,r){let a=v(t),s=e.session.part().name;return {namespaceUri:a,rootLocalName:b,nodeId:r??M(e.session.currentPackage(),s,a),label:n,data:o}}function k(e){let t=e.detail?`${e.reason}: ${e.detail}`:e.reason;return {ok:false,code:me.has(e.reason)?"invalidArgs":"unsupported",reason:t}}var me=new Set(["payload-too-large","unaddressable-payload","store-not-authored","offset-out-of-range","invalid-range","invalid-property-value","splits-surrogate-pair"]);function A(e,t,o){let n,r;if(o!==void 0)try{n=e.text?.(o),r=e.tagAttrs?.(o);}catch(s){return {reason:`${e.name} could not describe this payload: ${s instanceof Error?s.message:String(s)}`}}let a=t.text??n;return a===void 0?{reason:e.text?`${e.name} derives its text from \`data\`, so pass one \u2014 or pass \`text\` directly`:`${e.name} declares no \`text\`, so \`text\` is required`}:t.attrs===void 0&&e.tagAttrs&&r===void 0?{reason:`${e.name} derives its tag attrs from \`data\`, so pass one \u2014 or pass \`attrs\` directly`}:{attrs:t.attrs??r??{},text:a}}function C(e){return e.getEditingMode()==="viewing"?{ok:false,code:"locked",reason:"the document is open for viewing"}:null}function P(e){let t=e.getActiveScope();if(t.kind==="headerFooter")return {kind:"headerFooter",rId:t.rId};if(t.kind==="note"){let o=parseNoteScopeId(t.id);if(o)return {kind:"notesPart",noteKind:o.noteKind}}return {kind:"body"}}function T(e,t){let o=G(e),n=t===void 0?"":t.slice(0,t.indexOf("#"));if(!o||n.length===0)return P(e);if(n===o.session.part().name)return {kind:"body"};for(let r of o.session.headerFooterResolutionBySection())for(let a of [r.headers,r.footers])for(let s of a.values())if(s.partName===n)return {kind:"headerFooter",rId:s.rId};for(let r of ["footnote","endnote"])if(o.session.partFor({kind:"notesPart",noteKind:r})?.name===n)return {kind:"notesPart",noteKind:r};return P(e)}function it(e,t,o={}){let n=G(e);if(!n)return {ok:false,code:"notFound",reason:"no document is mounted"};let r=E(t,o.data);if(r&&!r.ok)return S(r.reason,r.issues);let a=A(t,o,r?.value);if("reason"in a)return {ok:false,code:"invalidArgs",reason:a.reason};let s=h(t.tagPrefix,t.name,a.attrs);if(!s.ok)return {ok:false,code:"invalidArgs",reason:`the encoded tag is ${s.length} characters; Word caps w:tag at 64 \u2014 move what does not fit into the payload (\`data\`), or shorten the attrs`};let d=C(e);if(d)return d;let i=o.at??n.state().selection.head,c=o.lock===void 0?"contentLocked":o.lock,u=P(e),l=n.session.insertCustomNode({paragraphId:i.paragraphId,offset:i.offset,tag:s.tag,text:a.text,...o.alias===void 0?{}:{alias:o.alias},...c===false?{}:{lock:c},...r?{payload:D(n,t,r.serialized,a.text)}:{}},u);return l.ok?{ok:true,changed:true,...l.nodeId===void 0?{}:{nodeId:l.nodeId}}:k(l)}function J(e){return e.surface??null}function Ne(e,t){let o=null,n=a=>a.id===t?true:a.kind==="textValue"?false:a.children.some(n),r=(a,s)=>{if(!(o||a.kind==="textValue"||s>64)){if(a.kind==="paragraph"){n(a)&&(o=a);return}for(let d of a.children)r(d,s+1);}};return r(e.root,0),o}function xe(e,t){let o=new Set,n=i=>{if(o.add(i.id),i.kind!=="textValue")for(let c of i.children)n(c);},r=i=>{if(i.id===t)return i;if(i.kind==="textValue")return null;for(let c of i.children){let u=r(c);if(u)return u}return null},a=r(e);if(!a)return null;n(a);let s=Number.MAX_SAFE_INTEGER,d=-1;for(let i of segmentsOf(e))o.has(i.runId)&&(i.start<s&&(s=i.start),i.end>d&&(d=i.end));return d<0?null:{start:s,end:d}}function gt(e,t){let o=J(e);if(!o)return {ok:false,code:"notFound",reason:"no document is mounted"};let n=C(e);if(n)return n;let r=o.session.removeCustomNode(t,T(e,t));return r.ok?{ok:true,changed:true}:k(r)}function ht(e,t,o,n={}){let r=J(e);if(!r)return {ok:false,code:"notFound",reason:"no document is mounted"};let a=C(e);if(a)return a;let s=T(e,o),d=r.session.partFor(s)??r.session.part(),i=Ne(d,o),c=i?xe(i,o):null,u=Se(r,o,s);if(!i||!c||!u)return {ok:false,code:"notFound",reason:"no custom node with that id"};let l=`${t.tagPrefix}:${t.name}`;if(u.identity!==null&&u.identity!==l)return {ok:false,code:"invalidArgs",reason:`node ${o} is a ${u.identity}, not a ${l}`};let f=ke(r,o),m=n.data===null?null:n.data===void 0?ve(f):E(t,n.data);if(m&&!m.ok)return S(m.reason,m.issues);let p=A(t,{attrs:n.attrs??(n.data===void 0?u.attrs:void 0),text:n.text??(m===null&&n.data===null?u.text:void 0)},m?.value);if("reason"in p)return {ok:false,code:"invalidArgs",reason:p.reason};let O=h(t.tagPrefix,t.name,p.attrs);if(!O.ok)return {ok:false,code:"invalidArgs",reason:`the encoded tag is ${O.length} characters; Word caps w:tag at 64 \u2014 move what does not fit into the payload (\`data\`), or shorten the attrs`};let $=n.alias??u.alias,w=n.lock??u.lock,y=r.session.insertCustomNode({replaceControlId:o,paragraphId:i.id,offset:c.start,tag:O.tag,text:p.text,...$===void 0?{}:{alias:$},...w===false||w===void 0?{}:{lock:w},...m?{payload:D(r,t,m.serialized,p.text,f?.nodeId)}:{}},s);return y.ok?{ok:true,changed:true,...y.nodeId===void 0?{}:{nodeId:y.nodeId}}:k(y)}function Se(e,t,o){let n=e.session.partFor(o)??e.session.part(),r=contentControlsIn(n.root).find(i=>i.node.id===t);if(!r)return null;let a=contentControlPropertiesOf(r.node),s=a.tag===void 0?null:N(a.tag),d=a.lock;return {identity:s?`${s.prefix}:${s.name}`:null,attrs:s?.attrs??{},text:contentControlTextOf(r.node),alias:a.alias,lock:d==="sdtLocked"||d==="sdtContentLocked"||d==="contentLocked"?d:d==="unlocked"?false:void 0}}function ve(e){if(!e)return null;let t=x(void 0,e.data);return t.ok?{ok:true,serialized:e.data,value:t.value}:{ok:false,reason:`the stored payload is not readable: ${t.issues.join(", ")}`,issues:[]}}function ke(e,t){let o=e.session.part();return customNodePayloadsByControl(e.session.currentPackage(),o.name).get(t)}export{Oe as a,h as b,N as c,Ie as d,x as e,R as f,b as g,v as h,L as i,z as j,V as k,Ve as l,ze as m,W as n,ot as o,it as p,gt as q,ht as r};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'use strict';var store=require('@docx-editor.dev/core/store'),layout=require('@docx-editor.dev/core/layout');function g(e){}var Oe=64,H=new Set(["__proto__","constructor","prototype"]);function h(e,t,o){let n=new URLSearchParams;for(let[s,d]of Object.entries(o))n.set(s,d);let r=n.toString(),a=r.length>0?`${e}:${t}?${r}`:`${e}:${t}`;return a.length>64?{ok:false,reason:"tag-overflow",length:a.length}:{ok:true,tag:a}}function N(e){if(e.length>64)return null;let t=e.indexOf(":");if(t<=0)return null;let o=e.slice(0,t),n=e.slice(t+1),r=n.indexOf("?"),a=r===-1?n:n.slice(0,r);if(a.length===0)return null;let s=Object.create(null);if(r!==-1)for(let[d,i]of new URLSearchParams(n.slice(r+1))){if(H.has(d))return null;s[d]=i;}return {prefix:o,name:a,attrs:s}}var Ie=262144;function x(e,t){if(t.length===0)return {ok:false,reason:"malformed",issues:["the node carries no payload"]};if(t.length>262144)return {ok:false,reason:"malformed",issues:[`the payload is ${String(t.length)} characters; the cap is ${String(262144)}`]};let o;try{o=JSON.parse(t);}catch(a){return {ok:false,reason:"malformed",issues:[a instanceof Error?a.message:"the payload is not JSON"]}}let n=I(o);if(!e)return {ok:true,value:n};let r=e["~standard"].validate(n);return B(r)?{ok:false,reason:"async",issues:["the schema validates asynchronously, which the read path cannot await"]}:r.issues?{ok:false,reason:"invalid",issues:r.issues.map(q)}:{ok:true,value:r.value}}function R(e){let t;try{t=JSON.stringify(e)??"";}catch(o){return {ok:false,reason:"malformed",issues:[o instanceof Error?o.message:"the value cannot be serialized"]}}return t.length===0?{ok:false,reason:"malformed",issues:["the value serializes to nothing"]}:t.length>262144?{ok:false,reason:"malformed",issues:[`the payload is ${String(t.length)} characters; the cap is ${String(262144)}`]}:{ok:true,value:t}}function B(e){return typeof e.then=="function"}function q(e){let t=(e.path??[]).map(o=>String(typeof o=="object"?o.key:o)).join(".");return t.length>0?`${t}: ${e.message}`:e.message}var Y=new Set(["__proto__","constructor","prototype"]);function I(e){if(Array.isArray(e))return e.map(I);if(e===null||typeof e!="object")return e;let t=Object.create(null);for(let[o,n]of Object.entries(e))Y.has(o)||(t[o]=I(n));return t}function _(e){let t=(e.path??[]).map(o=>{let n=typeof o=="object"?o.key:o;return typeof n=="number"?n:String(n)});return {message:e.message,path:t,pointer:t.join(".")}}function S(e,t){return {ok:false,code:"invalidArgs",reason:e,issues:t}}var b="docxEditor";function v(e){return e.payloadNamespace??`urn:docx-editor.dev:custom-node:${e.tagPrefix}`}function M(e,t,o){let n=0;for(let r of store.customNodePayloadsOf(e,t,o).keys()){let a=/^cx(\d{1,9})$/.exec(r);if(!a)continue;let s=Number(a[1]);s>n&&(n=s);}return `cx${String(n+1)}`}function L(e,t){let o=R(t);if(!o.ok)return {ok:false,reason:`the payload cannot be serialized: ${o.issues.join(", ")}`,issues:[]};if(!e.schema)return {ok:true,data:o.value,value:t};let r=e.schema["~standard"].validate(JSON.parse(o.value));if(Q(r))return {ok:false,reason:`${e.name}'s schema validates asynchronously, which a write cannot await`,issues:[]};if(r.issues){let s=r.issues.map(_);return {ok:false,reason:`the payload does not match ${e.name}'s schema: `+s.map(d=>(d.pointer?`${d.pointer}: `:"")+d.message).join(", "),issues:s}}let a=R(r.value);return a.ok?{ok:true,data:a.value,value:r.value}:{ok:false,reason:`the payload cannot be serialized: ${a.issues.join(", ")}`,issues:[]}}function Q(e){return typeof e.then=="function"}function z(e){return typeof e=="object"&&e!==null&&typeof e.name=="string"&&typeof e.tagPrefix=="string"}var V=/^[A-Za-z0-9_.-]+$/,te=/['"<>&]|[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/;function oe(e){return e.length>0&&!te.test(e)}function Ve(e){if(!V.test(e.name??""))throw new Error(`defineCustomNode: invalid name ${JSON.stringify(e.name)}`);if(!V.test(e.tagPrefix??""))throw new Error(`defineCustomNode: invalid tagPrefix ${JSON.stringify(e.tagPrefix)}`);let t=e.schema;if(t!==void 0&&typeof t?.["~standard"]?.validate!="function")throw new Error(`defineCustomNode: ${JSON.stringify(e.name)} has a schema that does not implement Standard Schema`);if(e.payloadNamespace!==void 0&&!oe(e.payloadNamespace))throw new Error(`defineCustomNode: ${JSON.stringify(e.name)} has a payloadNamespace that cannot be written into an XPath \u2014 no quotes, angle brackets, ampersands or control characters: ${JSON.stringify(e.payloadNamespace)}`);if(e.preserveOnExport!==void 0&&e.preserveOnExport!==true&&e.preserveOnExport!==false&&e.preserveOnExport!=="text")throw new Error(`defineCustomNode: ${JSON.stringify(e.name)} has an unknown preserveOnExport ${JSON.stringify(e.preserveOnExport)}`);return Object.freeze({...e,dataOf:n=>{if(!n||n.data===void 0||n.name!==void 0&&n.name!==e.name)return;if(!e.schema)return n.data;let r=e.schema["~standard"].validate(n.data);if(typeof r.then=="function")return;let a=r;return a.issues?void 0:a.value}})}function ze(e){g(e.licenseKey);let t=new Set;for(let o of e.nodes){let n=`${o.tagPrefix}:${o.name}`;if(t.has(n))throw new Error(`customNodesModule: two definitions claim ${JSON.stringify(n)}`);t.add(n);}return {id:"custom-nodes",customNodes:e.nodes,...e.onDiagnostic?{onCustomNodeDiagnostic:o=>{e.onDiagnostic?.(o);}}:{},customNodePayloadNamespaces:[...new Set(e.nodes.map(v))]}}function ne(e){for(let t of e.children)if(!(t.kind==="textValue"||t.localName!=="sdtPr")){for(let o of t.children)if(!(o.kind==="textValue"||o.localName!=="tag")){for(let n of o.attributes)if(n.localName==="val")return n.value}}}function j(e){if(e.kind==="textValue")return e.value;let t="";for(let o of e.children)t+=j(o);return t}function W(e,t,o={}){if(t.length===0)return [];let n=new Map;for(let s of t){let d=`${s.tagPrefix}:${s.name}`;if(n.has(d))throw new Error(`recognizeCustomNodes: duplicate definition for ${JSON.stringify(d)}`);n.set(d,s);}let r=[],a=(s,d)=>{if(!(s.kind==="textValue"||d>64)){if(s.kind==="contentControl"){let i=ne(s),c=i!==void 0?N(i):null,u=c?n.get(`${c.prefix}:${c.name}`):void 0;if(c&&u&&i!==void 0){let l=j(s),f=re(u,s.id,o.payloads?.get(s.id),store.contentControlPropertiesOf(s).dataBinding!==void 0,o.onDiagnostic),m=u.fromDocx?u.fromDocx({attrs:c.attrs,text:l,...f.present?{data:f.value}:{}}):c.attrs;if(m!==null){r.push({name:u.name,attrs:m,text:l,nodeId:s.id,tag:i,...f.present?{data:f.value}:{}});return}}}for(let i of s.children)a(i,d+1);}};return a(e.root,0),r}function re(e,t,o,n,r){if(!o)return n&&r?.({code:"payload-missing",name:e.name,nodeId:t,issues:["the control binds a store node this document does not hold"]}),{present:false};let a=x(e.schema,o.data);return a.ok?{present:true,value:a.value}:(r?.({code:"payload-invalid",name:e.name,nodeId:t,issues:a.issues}),{present:false})}function K(e,t){let o=store.findNode(e,t);return !o||o.kind!=="paragraph"?[]:store.revisionItemsOf({id:e.id,name:e.name,contentType:e.contentType,root:o})}function ce(e,t,o,n){if(t.length===0)return [];let r=W(e,t,{...o===void 0?{}:{payloads:o},...n===void 0?{}:{onDiagnostic:n}});if(r.length===0)return [];let a=store.locateSites(e),s=[];for(let d of r){let i=t.find(l=>l.name===d.name&&d.tag.startsWith(`${l.tagPrefix}:`));if(!i)continue;let c=i.reviewCard?i.reviewCard({attrs:d.attrs,text:d.text,...d.data===void 0?{}:{data:d.data}}):null;if(i.reviewCard&&c===null)continue;let u=a.get(d.nodeId);s.push({kind:"custom",id:d.nodeId,name:d.name,tag:d.tag,attrs:d.attrs,text:d.text,...d.data===void 0?{}:{data:d.data},carded:i.reviewCard!==void 0&&c!==null,title:c?.title??"",...c?.detail!==void 0?{detail:c.detail}:{},range:u?{partName:e.name,start:{paragraphId:u.paragraphId,offset:u.start},end:{paragraphId:u.paragraphId,offset:u.end}}:null});}return s}function U(e){let t=store.collectReviewItems(e),o=(e.customNodes??[]).filter(z);if(o.length===0)return t;let n=[e.storyPart],r=new Set([e.storyPart.name]);for(let i of e.furnitureParts??[])r.has(i.name)||(r.add(i.name),n.push(i));let a=[],s=new Map;for(let i of n){a.push(...ce(i,o,i.name===e.storyPart.name?e.customNodePayloads:void 0,e.reportCustomNodeDiagnostic));let c=s.size;for(let[u,l]of store.paragraphOrderOfPart(i))s.has(u)||s.set(u,c+l);}return a.length===0?t:[...t,...a].sort((i,c)=>layout.reviewItemPositionRank(i,s)-layout.reviewItemPositionRank(c,s))}function ot(e={}){return g(e.licenseKey),{id:"review",review:{displayModes:["all-markup","proposed","original"],collectReviewItems:U,revisionItemsOfParagraph:K}}}function G(e){return e.surface??null}function E(e,t){if(t==null)return null;let o=L(e,t);return o.ok?{ok:true,serialized:o.data,value:o.value}:{ok:false,reason:o.reason,issues:o.issues}}function D(e,t,o,n,r){let a=v(t),s=e.session.part().name;return {namespaceUri:a,rootLocalName:b,nodeId:r??M(e.session.currentPackage(),s,a),label:n,data:o}}function k(e){let t=e.detail?`${e.reason}: ${e.detail}`:e.reason;return {ok:false,code:me.has(e.reason)?"invalidArgs":"unsupported",reason:t}}var me=new Set(["payload-too-large","unaddressable-payload","store-not-authored","offset-out-of-range","invalid-range","invalid-property-value","splits-surrogate-pair"]);function A(e,t,o){let n,r;if(o!==void 0)try{n=e.text?.(o),r=e.tagAttrs?.(o);}catch(s){return {reason:`${e.name} could not describe this payload: ${s instanceof Error?s.message:String(s)}`}}let a=t.text??n;return a===void 0?{reason:e.text?`${e.name} derives its text from \`data\`, so pass one \u2014 or pass \`text\` directly`:`${e.name} declares no \`text\`, so \`text\` is required`}:t.attrs===void 0&&e.tagAttrs&&r===void 0?{reason:`${e.name} derives its tag attrs from \`data\`, so pass one \u2014 or pass \`attrs\` directly`}:{attrs:t.attrs??r??{},text:a}}function C(e){return e.getEditingMode()==="viewing"?{ok:false,code:"locked",reason:"the document is open for viewing"}:null}function P(e){let t=e.getActiveScope();if(t.kind==="headerFooter")return {kind:"headerFooter",rId:t.rId};if(t.kind==="note"){let o=store.parseNoteScopeId(t.id);if(o)return {kind:"notesPart",noteKind:o.noteKind}}return {kind:"body"}}function T(e,t){let o=G(e),n=t===void 0?"":t.slice(0,t.indexOf("#"));if(!o||n.length===0)return P(e);if(n===o.session.part().name)return {kind:"body"};for(let r of o.session.headerFooterResolutionBySection())for(let a of [r.headers,r.footers])for(let s of a.values())if(s.partName===n)return {kind:"headerFooter",rId:s.rId};for(let r of ["footnote","endnote"])if(o.session.partFor({kind:"notesPart",noteKind:r})?.name===n)return {kind:"notesPart",noteKind:r};return P(e)}function it(e,t,o={}){let n=G(e);if(!n)return {ok:false,code:"notFound",reason:"no document is mounted"};let r=E(t,o.data);if(r&&!r.ok)return S(r.reason,r.issues);let a=A(t,o,r?.value);if("reason"in a)return {ok:false,code:"invalidArgs",reason:a.reason};let s=h(t.tagPrefix,t.name,a.attrs);if(!s.ok)return {ok:false,code:"invalidArgs",reason:`the encoded tag is ${s.length} characters; Word caps w:tag at 64 \u2014 move what does not fit into the payload (\`data\`), or shorten the attrs`};let d=C(e);if(d)return d;let i=o.at??n.state().selection.head,c=o.lock===void 0?"contentLocked":o.lock,u=P(e),l=n.session.insertCustomNode({paragraphId:i.paragraphId,offset:i.offset,tag:s.tag,text:a.text,...o.alias===void 0?{}:{alias:o.alias},...c===false?{}:{lock:c},...r?{payload:D(n,t,r.serialized,a.text)}:{}},u);return l.ok?{ok:true,changed:true,...l.nodeId===void 0?{}:{nodeId:l.nodeId}}:k(l)}function J(e){return e.surface??null}function Ne(e,t){let o=null,n=a=>a.id===t?true:a.kind==="textValue"?false:a.children.some(n),r=(a,s)=>{if(!(o||a.kind==="textValue"||s>64)){if(a.kind==="paragraph"){n(a)&&(o=a);return}for(let d of a.children)r(d,s+1);}};return r(e.root,0),o}function xe(e,t){let o=new Set,n=i=>{if(o.add(i.id),i.kind!=="textValue")for(let c of i.children)n(c);},r=i=>{if(i.id===t)return i;if(i.kind==="textValue")return null;for(let c of i.children){let u=r(c);if(u)return u}return null},a=r(e);if(!a)return null;n(a);let s=Number.MAX_SAFE_INTEGER,d=-1;for(let i of store.segmentsOf(e))o.has(i.runId)&&(i.start<s&&(s=i.start),i.end>d&&(d=i.end));return d<0?null:{start:s,end:d}}function gt(e,t){let o=J(e);if(!o)return {ok:false,code:"notFound",reason:"no document is mounted"};let n=C(e);if(n)return n;let r=o.session.removeCustomNode(t,T(e,t));return r.ok?{ok:true,changed:true}:k(r)}function ht(e,t,o,n={}){let r=J(e);if(!r)return {ok:false,code:"notFound",reason:"no document is mounted"};let a=C(e);if(a)return a;let s=T(e,o),d=r.session.partFor(s)??r.session.part(),i=Ne(d,o),c=i?xe(i,o):null,u=Se(r,o,s);if(!i||!c||!u)return {ok:false,code:"notFound",reason:"no custom node with that id"};let l=`${t.tagPrefix}:${t.name}`;if(u.identity!==null&&u.identity!==l)return {ok:false,code:"invalidArgs",reason:`node ${o} is a ${u.identity}, not a ${l}`};let f=ke(r,o),m=n.data===null?null:n.data===void 0?ve(f):E(t,n.data);if(m&&!m.ok)return S(m.reason,m.issues);let p=A(t,{attrs:n.attrs??(n.data===void 0?u.attrs:void 0),text:n.text??(m===null&&n.data===null?u.text:void 0)},m?.value);if("reason"in p)return {ok:false,code:"invalidArgs",reason:p.reason};let O=h(t.tagPrefix,t.name,p.attrs);if(!O.ok)return {ok:false,code:"invalidArgs",reason:`the encoded tag is ${O.length} characters; Word caps w:tag at 64 \u2014 move what does not fit into the payload (\`data\`), or shorten the attrs`};let $=n.alias??u.alias,w=n.lock??u.lock,y=r.session.insertCustomNode({replaceControlId:o,paragraphId:i.id,offset:c.start,tag:O.tag,text:p.text,...$===void 0?{}:{alias:$},...w===false||w===void 0?{}:{lock:w},...m?{payload:D(r,t,m.serialized,p.text,f?.nodeId)}:{}},s);return y.ok?{ok:true,changed:true,...y.nodeId===void 0?{}:{nodeId:y.nodeId}}:k(y)}function Se(e,t,o){let n=e.session.partFor(o)??e.session.part(),r=store.contentControlsIn(n.root).find(i=>i.node.id===t);if(!r)return null;let a=store.contentControlPropertiesOf(r.node),s=a.tag===void 0?null:N(a.tag),d=a.lock;return {identity:s?`${s.prefix}:${s.name}`:null,attrs:s?.attrs??{},text:store.contentControlTextOf(r.node),alias:a.alias,lock:d==="sdtLocked"||d==="sdtContentLocked"||d==="contentLocked"?d:d==="unlocked"?false:void 0}}function ve(e){if(!e)return null;let t=x(void 0,e.data);return t.ok?{ok:true,serialized:e.data,value:t.value}:{ok:false,reason:`the stored payload is not readable: ${t.issues.join(", ")}`,issues:[]}}function ke(e,t){let o=e.session.part();return store.customNodePayloadsByControl(e.session.currentPackage(),o.name).get(t)}exports.a=Oe;exports.b=h;exports.c=N;exports.d=Ie;exports.e=x;exports.f=R;exports.g=b;exports.h=v;exports.i=L;exports.j=z;exports.k=V;exports.l=Ve;exports.m=ze;exports.n=W;exports.o=ot;exports.p=it;exports.q=gt;exports.r=ht;
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import { EditorModule } from '@docx-editor.dev/core/editor';
|
|
2
|
+
import { OoxmlPart } from '@docx-editor.dev/core/store';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Licensing, v1: honor system.
|
|
6
|
+
*
|
|
7
|
+
* The key is accepted and remembered so that adding offline (Ed25519)
|
|
8
|
+
* verification later is not a breaking change — but nothing validates it,
|
|
9
|
+
* nothing warns, nothing renders differently, and NOTHING EVER LEAVES THE
|
|
10
|
+
* PROCESS: no network request is made for licensing, ever. That last property
|
|
11
|
+
* is a spec requirement (`pro-licensing`), not an implementation detail.
|
|
12
|
+
*/
|
|
13
|
+
/** Accepted by every pro entry point. */
|
|
14
|
+
interface ProLicenseOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Your license key from docx-editor.dev. Optional in v1: unlicensed use in
|
|
17
|
+
* development and evaluation is permitted, production use requires a
|
|
18
|
+
* license (see LICENSE.md) — the package trusts you either way.
|
|
19
|
+
*/
|
|
20
|
+
readonly licenseKey?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The review module: comments, tracked changes, and markup rendering as an
|
|
25
|
+
* `EditorModule` for `createDocxEditor({ modules })`.
|
|
26
|
+
*
|
|
27
|
+
* Registering it is the whole enablement story: the review chrome slots light
|
|
28
|
+
* up through the same `toolbarCommandState` they were disabled by, suggesting
|
|
29
|
+
* mode becomes reachable, and the editor renders revisions in markup rather
|
|
30
|
+
* than the free tier's final-state projection.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* How {@link reviewModule} is configured. Carries only the licence key today, so
|
|
35
|
+
* `reviewModule()` with no argument is the ordinary call.
|
|
36
|
+
*
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
interface ReviewModuleOptions extends ProLicenseOptions {
|
|
40
|
+
}
|
|
41
|
+
/** Build the review module. Construction never validates the key and never touches the network. */
|
|
42
|
+
declare function reviewModule(options?: ReviewModuleOptions): EditorModule;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The Standard Schema interface, vendored.
|
|
46
|
+
*
|
|
47
|
+
* Any zod, valibot or arktype schema satisfies it. See https://standardschema.dev.
|
|
48
|
+
*
|
|
49
|
+
* Reduced to the parts used here rather than copied: the spec's namespace, `Props`,
|
|
50
|
+
* `SuccessResult`/`FailureResult`, `PathSegment` and `InferInput` are all absent. Assignability
|
|
51
|
+
* with a real schema is what matters, and is checked against zod in the tests.
|
|
52
|
+
*/
|
|
53
|
+
interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
54
|
+
readonly '~standard': {
|
|
55
|
+
readonly version: 1;
|
|
56
|
+
readonly vendor: string;
|
|
57
|
+
readonly validate: (value: unknown) => StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;
|
|
58
|
+
readonly types?: {
|
|
59
|
+
readonly input: Input;
|
|
60
|
+
readonly output: Output;
|
|
61
|
+
} | undefined;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** What a Standard Schema validation answers. */
|
|
65
|
+
type StandardSchemaResult<Output> = {
|
|
66
|
+
readonly value: Output;
|
|
67
|
+
readonly issues?: undefined;
|
|
68
|
+
} | {
|
|
69
|
+
readonly issues: readonly StandardSchemaIssue[];
|
|
70
|
+
};
|
|
71
|
+
/** One validation failure. `path` is what tells a host WHICH field was wrong. */
|
|
72
|
+
interface StandardSchemaIssue {
|
|
73
|
+
readonly message: string;
|
|
74
|
+
readonly path?: readonly (PropertyKey | {
|
|
75
|
+
readonly key: PropertyKey;
|
|
76
|
+
})[] | undefined;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The type a schema produces, for a definition to hand back to its host.
|
|
80
|
+
*
|
|
81
|
+
* `unknown` for a definition with no schema, which is the honest description of an unchecked
|
|
82
|
+
* payload — not `never`, which would make the field unusable rather than merely unguaranteed.
|
|
83
|
+
*/
|
|
84
|
+
type InferSchemaOutput<Schema> = 0 extends 1 & Schema ? any : Schema extends StandardSchemaV1<unknown, infer Output> ? Output : unknown;
|
|
85
|
+
/**
|
|
86
|
+
* The type a schema ACCEPTS, which is what a write has to satisfy.
|
|
87
|
+
*
|
|
88
|
+
* Different from the output whenever the schema transforms — a zod `.default()` or `.transform()`
|
|
89
|
+
* takes one shape and produces another — so a write typed by the output would reject the very
|
|
90
|
+
* value the schema was written to accept.
|
|
91
|
+
*/
|
|
92
|
+
type InferSchemaInput<Schema> = 0 extends 1 & Schema ? any : Schema extends StandardSchemaV1<infer Input, unknown> ? Input : unknown;
|
|
93
|
+
/**
|
|
94
|
+
* Why a payload was refused.
|
|
95
|
+
*
|
|
96
|
+
* `malformed` is not valid JSON at all; `invalid` parsed but did not match the schema; `async`
|
|
97
|
+
* is a schema whose validation returns a promise, which cannot be used here (see below).
|
|
98
|
+
*/
|
|
99
|
+
type CustomNodeDataRejection = 'malformed' | 'invalid' | 'async';
|
|
100
|
+
/** What {@link parseCustomNodeData} answers. */
|
|
101
|
+
type CustomNodeDataResult<Output> = {
|
|
102
|
+
readonly ok: true;
|
|
103
|
+
readonly value: Output;
|
|
104
|
+
} | {
|
|
105
|
+
readonly ok: false;
|
|
106
|
+
readonly reason: CustomNodeDataRejection;
|
|
107
|
+
/** Human-readable, for a host to log. Never rendered as markup by this package. */
|
|
108
|
+
readonly issues: readonly string[];
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* The largest payload this will parse, in UTF-16 code units.
|
|
112
|
+
*
|
|
113
|
+
* A file-supplied length must never reach an allocation, and `JSON.parse` on a hostile string
|
|
114
|
+
* is the allocation. 256 KB is far past any legitimate chip payload and far short of anything
|
|
115
|
+
* that hurts.
|
|
116
|
+
*/
|
|
117
|
+
declare const MAX_CUSTOM_NODE_DATA_LENGTH: number;
|
|
118
|
+
/**
|
|
119
|
+
* Parse a payload out of a data part and validate it against the definition's schema.
|
|
120
|
+
*
|
|
121
|
+
* Synchronous on purpose. This runs inside the read path, where recognition happens for every
|
|
122
|
+
* node in the document before anything paints, and an async boundary there would mean a
|
|
123
|
+
* document that renders its chips a frame later than its text. A schema with an async refinement
|
|
124
|
+
* is refused (`async`) rather than awaited, so the limitation is visible instead of silent.
|
|
125
|
+
*
|
|
126
|
+
* A payload with no schema comes back as the parsed JSON, typed `unknown` — the host asked for
|
|
127
|
+
* no guarantees and gets none, rather than getting a lie. It is also a NULL-PROTOTYPE object on
|
|
128
|
+
* that path, where a schema-validated one is whatever the validator rebuilt: `hasOwnProperty`
|
|
129
|
+
* and `instanceof Object` do not hold on the former.
|
|
130
|
+
*/
|
|
131
|
+
declare function parseCustomNodeData<Schema extends StandardSchemaV1 | undefined>(schema: Schema, raw: string): CustomNodeDataResult<Schema extends StandardSchemaV1 ? InferSchemaOutput<Schema> : unknown>;
|
|
132
|
+
/**
|
|
133
|
+
* Serialize a payload for a data part. Refuses what cannot round-trip through JSON.
|
|
134
|
+
*
|
|
135
|
+
* NOT symmetric with {@link parseCustomNodeData}: a key named `__proto__`, `constructor` or
|
|
136
|
+
* `prototype` is written here and dropped on the way back, because a payload arriving from a
|
|
137
|
+
* file is the hazard and a payload leaving this process is not. A host that needs those keys
|
|
138
|
+
* needs a different name for them.
|
|
139
|
+
*/
|
|
140
|
+
declare function serializeCustomNodeData(value: unknown): CustomNodeDataResult<string>;
|
|
141
|
+
|
|
142
|
+
/** A recognized custom node: one inline SDT whose tag matched a definition. */
|
|
143
|
+
interface RecognizedCustomNode {
|
|
144
|
+
/** The definition's `name`. */
|
|
145
|
+
readonly name: string;
|
|
146
|
+
/** Attrs after the definition's `fromDocx` had its say. Untrusted input. */
|
|
147
|
+
readonly attrs: Readonly<Record<string, string>>;
|
|
148
|
+
/** The SDT's literal content text — what Word users see and may have edited. */
|
|
149
|
+
readonly text: string;
|
|
150
|
+
/** The SDT node's stable id in the canonical tree. */
|
|
151
|
+
readonly nodeId: string;
|
|
152
|
+
/** The raw `w:tag` the node was recognized from. */
|
|
153
|
+
readonly tag: string;
|
|
154
|
+
/**
|
|
155
|
+
* The payload the node's control binds to, validated against the definition's `schema`.
|
|
156
|
+
*
|
|
157
|
+
* `undefined` when the node carries none, when the binding named a store node the document
|
|
158
|
+
* does not hold, or when the payload failed its schema — the last of which is reported
|
|
159
|
+
* through {@link customNodesModule}'s `onDiagnostic` rather than swallowed. A chip that
|
|
160
|
+
* vanished because one field was wrong would be worse than a chip with no data.
|
|
161
|
+
*
|
|
162
|
+
* With a schema declared this is that schema's output type; without one it is whatever JSON
|
|
163
|
+
* the file held, which is the honest description of an unchecked payload.
|
|
164
|
+
*/
|
|
165
|
+
readonly data?: unknown;
|
|
166
|
+
}
|
|
167
|
+
/** A payload as the store holds it, before any schema has looked at it. Untrusted file input. */
|
|
168
|
+
interface CustomNodePayloadSource {
|
|
169
|
+
readonly nodeId: string;
|
|
170
|
+
readonly label: string;
|
|
171
|
+
readonly data: string;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Something worth telling an integrator about a document, which is never worth throwing over.
|
|
175
|
+
*
|
|
176
|
+
* A payload arrives from a file the sender wrote, so "it did not match the schema" is an
|
|
177
|
+
* ordinary property of an ordinary document — not an exception. It is reported and the node
|
|
178
|
+
* still renders.
|
|
179
|
+
*/
|
|
180
|
+
interface CustomNodeDiagnostic {
|
|
181
|
+
/**
|
|
182
|
+
* `payload-invalid` — a payload was found and did not match the schema.
|
|
183
|
+
* `payload-missing` — the control's binding names a store node the document does not hold.
|
|
184
|
+
*
|
|
185
|
+
* The second is what a half-stripped export or a hand-edited file leaves behind, and it used
|
|
186
|
+
* to be indistinguishable from "this node carries no payload": both arrive as `data:
|
|
187
|
+
* undefined` and neither said anything.
|
|
188
|
+
*/
|
|
189
|
+
readonly code: 'payload-invalid' | 'payload-missing';
|
|
190
|
+
/** The definition whose schema refused it. */
|
|
191
|
+
readonly name: string;
|
|
192
|
+
/** The control's canonical node id, so a host can locate it. */
|
|
193
|
+
readonly nodeId: string;
|
|
194
|
+
/** Human-readable, one per failing field. Never rendered as markup by this package. */
|
|
195
|
+
readonly issues: readonly string[];
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* One integrator-defined inline node, anchored on a run-level SDT whose `w:tag` carries its
|
|
199
|
+
* identity.
|
|
200
|
+
*
|
|
201
|
+
* A definition claims a tag PREFIX, so `acme` recognizes every `acme:*` tag. An SDT whose prefix
|
|
202
|
+
* no definition claims stays literal — which is also what the free tier and Word itself render,
|
|
203
|
+
* so an unrecognized node never loses content or locks editing.
|
|
204
|
+
*
|
|
205
|
+
* Build one with {@link defineCustomNode}, which validates the shape, then register it through
|
|
206
|
+
* {@link customNodesModule}.
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```ts
|
|
210
|
+
* const citation = defineCustomNode({
|
|
211
|
+
* name: 'citation',
|
|
212
|
+
* tagPrefix: 'acme',
|
|
213
|
+
* chrome: { color: '#2563eb' },
|
|
214
|
+
* onClick: (node) => openCitation(node.attrs.key),
|
|
215
|
+
* });
|
|
216
|
+
* ```
|
|
217
|
+
*
|
|
218
|
+
* @public
|
|
219
|
+
*/
|
|
220
|
+
interface CustomNodeDefinition<Schema extends StandardSchemaV1 | undefined = any> {
|
|
221
|
+
/** Node type name — the second segment of the tag (`<prefix>:<name>?…`). */
|
|
222
|
+
readonly name: string;
|
|
223
|
+
/** Tag prefix this definition claims (`acme` claims `acme:*`). No colons. */
|
|
224
|
+
readonly tagPrefix: string;
|
|
225
|
+
/**
|
|
226
|
+
* What the document SHOWS for this node, from its payload.
|
|
227
|
+
*
|
|
228
|
+
* The one thing most definitions need beyond an identity and a schema:
|
|
229
|
+
*
|
|
230
|
+
* ```ts
|
|
231
|
+
* defineCustomNode({
|
|
232
|
+
* name: 'citation',
|
|
233
|
+
* tagPrefix: 'docx',
|
|
234
|
+
* schema: CitationData,
|
|
235
|
+
* text: (data) => `(${data.authors[0]} ${data.year})`,
|
|
236
|
+
* });
|
|
237
|
+
* ```
|
|
238
|
+
*
|
|
239
|
+
* With it, a write takes the payload alone — `insertCustomNode(editor, Citation, { data })` —
|
|
240
|
+
* and the words in the paragraph are computed, so they cannot drift from the data they
|
|
241
|
+
* describe. Without it, pass `text` on every call and keep the two in step yourself.
|
|
242
|
+
*
|
|
243
|
+
* Word paints a bound control's text from the payload and will not let a user type into it,
|
|
244
|
+
* so this is the only thing that decides what a reader sees.
|
|
245
|
+
*/
|
|
246
|
+
readonly text?: (data: InferSchemaOutput<Schema>) => string;
|
|
247
|
+
/**
|
|
248
|
+
* Extra identity to put in the `w:tag`, from the payload. Rarely needed.
|
|
249
|
+
*
|
|
250
|
+
* The tag already carries `<prefix>:<name>`, which is what recognition matches on, so most
|
|
251
|
+
* nodes need nothing here. Add it when a reader that opens the document WITHOUT the payload
|
|
252
|
+
* store should still be able to tell which one this is — a `sourceId` on a citation, say.
|
|
253
|
+
*
|
|
254
|
+
* Word caps the encoded tag at 64 characters, prefix and name included.
|
|
255
|
+
*/
|
|
256
|
+
readonly tagAttrs?: (data: InferSchemaOutput<Schema>) => Readonly<Record<string, string>>;
|
|
257
|
+
/**
|
|
258
|
+
* Recognition hook. Receives the decoded attrs and the SDT's literal text
|
|
259
|
+
* (so label drift from Word edits is visible) and returns the attrs the node
|
|
260
|
+
* should carry — or null to leave this SDT unrecognized and literal.
|
|
261
|
+
*
|
|
262
|
+
* Every input value originates in a file an attacker controls; treat it as
|
|
263
|
+
* untrusted and never build DOM or URLs from it without sanitizing.
|
|
264
|
+
*/
|
|
265
|
+
readonly fromDocx?: (input: {
|
|
266
|
+
readonly attrs: Readonly<Record<string, string>>;
|
|
267
|
+
readonly text: string;
|
|
268
|
+
/**
|
|
269
|
+
* The bound payload, already through `schema` — so this is the type the definition
|
|
270
|
+
* declared, not `unknown`. Undefined when the node carries none or it did not match.
|
|
271
|
+
*/
|
|
272
|
+
readonly data?: InferSchemaOutput<Schema>;
|
|
273
|
+
}) => Readonly<Record<string, string>> | null;
|
|
274
|
+
/**
|
|
275
|
+
* Chip appearance, HOST-authored (never file data). `color` tints the chip
|
|
276
|
+
* and its border; applied by `CustomNodeChrome` from `@docx-editor.dev/pro/react`.
|
|
277
|
+
*/
|
|
278
|
+
readonly chrome?: {
|
|
279
|
+
readonly color?: string;
|
|
280
|
+
};
|
|
281
|
+
/** Click on the painted chip. UI state belongs in `CustomNodeChrome`'s `onNodeClick`. */
|
|
282
|
+
readonly onClick?: (node: ActivatedCustomNode) => void;
|
|
283
|
+
/** Pointer enters the painted chip. */
|
|
284
|
+
readonly onHover?: (node: ActivatedCustomNode) => void;
|
|
285
|
+
/**
|
|
286
|
+
* Contribute a card to the review sidebar for every recognized node of this
|
|
287
|
+
* definition, anchored at the node's range. Return null to skip one node.
|
|
288
|
+
*
|
|
289
|
+
* `attrs` and `text` originate in the file — untrusted; the returned strings
|
|
290
|
+
* are rendered as TEXT by the pane, never markup. The context-menu section
|
|
291
|
+
* reuses this hook for its info block and may invoke it with `text: ''` when
|
|
292
|
+
* no review module is registered (the DOM decode alone cannot see the text).
|
|
293
|
+
*/
|
|
294
|
+
readonly reviewCard?: (node: {
|
|
295
|
+
readonly attrs: Readonly<Record<string, string>>;
|
|
296
|
+
readonly text: string;
|
|
297
|
+
/** The bound payload, already through `schema` — see {@link CustomNodeDefinition.fromDocx}. */
|
|
298
|
+
readonly data?: InferSchemaOutput<Schema>;
|
|
299
|
+
}) => {
|
|
300
|
+
readonly title: string;
|
|
301
|
+
readonly detail?: string;
|
|
302
|
+
} | null;
|
|
303
|
+
/**
|
|
304
|
+
* The "Edit {label}" row the context menu shows at the top when the
|
|
305
|
+
* right-click lands on the node's chip. The HOST owns the dialog.
|
|
306
|
+
*
|
|
307
|
+
* Re-author with `updateCustomNode(editor, definition, node.nodeId, attrs, text, { data })`:
|
|
308
|
+
* one transaction, one undo step. The activation carries `nodeId`, the node's `text` and its
|
|
309
|
+
* `data`, which is everything a prefilled form needs.
|
|
310
|
+
*/
|
|
311
|
+
readonly onEdit?: (node: ActivatedCustomNode) => void;
|
|
312
|
+
/**
|
|
313
|
+
* Display name for chrome — the "Edit {label}" context-menu row. Defaults to
|
|
314
|
+
* `name`. Host-authored, never file data; provide a localized string.
|
|
315
|
+
*/
|
|
316
|
+
readonly label?: string;
|
|
317
|
+
/**
|
|
318
|
+
* The shape of this node's payload, as a zod (or valibot, or arktype) schema.
|
|
319
|
+
*
|
|
320
|
+
* A payload lives in a customXml data part, so it arrives from a file the sender controls.
|
|
321
|
+
* Declaring the shape means it is parsed and checked ONCE, at the read boundary, after which
|
|
322
|
+
* the `data` handed to the hooks is the type that was asked for rather than something every
|
|
323
|
+
* caller has to re-guard. Without one, `data` is whatever JSON the file held, typed
|
|
324
|
+
* `unknown`, which is the honest description of an unchecked payload.
|
|
325
|
+
*
|
|
326
|
+
* Any Standard Schema satisfies this, which is what zod produces:
|
|
327
|
+
*
|
|
328
|
+
* ```ts
|
|
329
|
+
* const Citation = z.object({ sourceId: z.string(), year: z.number() });
|
|
330
|
+
* defineCustomNode({ name: 'citation', tagPrefix: 'acme', schema: Citation });
|
|
331
|
+
* ```
|
|
332
|
+
*
|
|
333
|
+
* Validated on the way IN as well as on the way out, so a payload that does not match is
|
|
334
|
+
* refused at the insert rather than written and rejected on the next open.
|
|
335
|
+
*/
|
|
336
|
+
readonly schema?: Schema;
|
|
337
|
+
/**
|
|
338
|
+
* The customXml store this definition's payloads live in.
|
|
339
|
+
*
|
|
340
|
+
* One store per namespace, per document, so this is what decides whether two definitions
|
|
341
|
+
* share a store or get one each. Defaults to a namespace derived from `tagPrefix`, which
|
|
342
|
+
* means a host that never thinks about it still gets one store per prefix and never collides
|
|
343
|
+
* with another integrator's.
|
|
344
|
+
*
|
|
345
|
+
* Set it to interoperate with something that already reads a namespace of its own. Whatever
|
|
346
|
+
* it is, it must be free of quotes and angle brackets: it is written into an XPath prefix
|
|
347
|
+
* declaration, where there is no escape for either.
|
|
348
|
+
*/
|
|
349
|
+
readonly payloadNamespace?: string;
|
|
350
|
+
/**
|
|
351
|
+
* What happens to this node when a document is exported OUTSIDE the system that made it.
|
|
352
|
+
*
|
|
353
|
+
* A host may not want its own markup travelling in a file its users download: a `w:tag`
|
|
354
|
+
* naming the tool, or a payload with no meaning anywhere else. This declares the fate, and
|
|
355
|
+
* the save that applies it picks the pipeline — so one document can serialize one way at
|
|
356
|
+
* rest and another on the way out.
|
|
357
|
+
*
|
|
358
|
+
* - `true` (default) — the node and its payload survive untouched.
|
|
359
|
+
* - `'text'` — the control is unwrapped: a reader still sees the words, while the tag, the
|
|
360
|
+
* binding and the payload are gone. Right for a citation, whose text is the point of it.
|
|
361
|
+
* - `false` — the node goes, and takes its content with it.
|
|
362
|
+
*
|
|
363
|
+
* Applied by `prepareForExport`, which is a pipeline of its own rather than something
|
|
364
|
+
* `save()` does — that is what lets one document serialize one way at rest and another on the
|
|
365
|
+
* way out.
|
|
366
|
+
*
|
|
367
|
+
* IT DOES NOT MAKE A DOCUMENT ANONYMOUS. It removes this library's markup and nothing else. A
|
|
368
|
+
* `.docx` carries its origin in `docProps/app.xml`, `docProps/core.xml`, comment and revision
|
|
369
|
+
* authors, rsids and custom document properties.
|
|
370
|
+
*/
|
|
371
|
+
readonly preserveOnExport?: boolean | 'text';
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* A definition, plus what {@link defineCustomNode} attaches to it.
|
|
375
|
+
*
|
|
376
|
+
* You author a {@link CustomNodeDefinition}; you are handed one of these. The difference is
|
|
377
|
+
* `dataOf`, which cannot be written by hand because it closes over the schema you just declared.
|
|
378
|
+
*/
|
|
379
|
+
interface CustomNode<Schema extends StandardSchemaV1 | undefined = any> extends CustomNodeDefinition<Schema> {
|
|
380
|
+
/**
|
|
381
|
+
* This node's payload, from a surface that carries every definition's under one type.
|
|
382
|
+
*
|
|
383
|
+
* `RecognizedCustomNode.data`, `ActivatedCustomNode.data` and `ReviewCustomItem.data` are all
|
|
384
|
+
* `unknown`, because each of those can be any registered definition's node. This narrows one
|
|
385
|
+
* to THIS definition and validates its payload against THIS schema, so a host reads a typed
|
|
386
|
+
* value without importing its own validator at the call site:
|
|
387
|
+
*
|
|
388
|
+
* ```ts
|
|
389
|
+
* const survey = Iceberg.dataOf(node); // IcebergData | undefined
|
|
390
|
+
* ```
|
|
391
|
+
*
|
|
392
|
+
* `undefined` when the node is a different definition's, carries no payload, or holds one the
|
|
393
|
+
* schema rejects — the three cases a caller has to handle anyway.
|
|
394
|
+
*
|
|
395
|
+
* `name` is checked when present and never required, so this also works on a host's own object
|
|
396
|
+
* that kept only the payload:
|
|
397
|
+
*
|
|
398
|
+
* ```ts
|
|
399
|
+
* const survey = Iceberg.dataOf(popoverState); // { data } is enough
|
|
400
|
+
* ```
|
|
401
|
+
*/
|
|
402
|
+
readonly dataOf: (node: {
|
|
403
|
+
readonly name?: string;
|
|
404
|
+
readonly data?: unknown;
|
|
405
|
+
} | null | undefined) => InferSchemaOutput<Schema> | undefined;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* A definition of any payload shape, spelled out.
|
|
409
|
+
*
|
|
410
|
+
* The AUTHORED shape, which is what every collection and every internal helper takes: they read
|
|
411
|
+
* `name`, `schema`, `text` and `preserveOnExport` and never need `dataOf`. A {@link CustomNode}
|
|
412
|
+
* is assignable to it, so `defineCustomNode`'s result goes wherever this is asked for.
|
|
413
|
+
*
|
|
414
|
+
* The same thing bare `CustomNodeDefinition` already means — the interface defaults its
|
|
415
|
+
* parameter to `any` for exactly this reason. `CustomNodeDefinition<Schema>` is INVARIANT in
|
|
416
|
+
* `Schema`, because the schema's output type appears in the PARAMETER of `fromDocx` and
|
|
417
|
+
* `reviewCard`; that is what makes those hooks typed, and it also means two definitions with
|
|
418
|
+
* different schemas are not assignable to one another. Had the default been `undefined`, the
|
|
419
|
+
* obvious annotation — `const nodes: CustomNodeDefinition[] = [citation, figure]` — would fail
|
|
420
|
+
* with a message naming neither the cause nor this alias.
|
|
421
|
+
*
|
|
422
|
+
* The cost, stated plainly: `data` is unchecked wherever a definition is held under this type.
|
|
423
|
+
* Pull one out of a registry and `insertCustomNode(editor, def, attrs, text, { data })` accepts
|
|
424
|
+
* any shape at all. Payload typing lives where the definition is WRITTEN — `defineCustomNode`
|
|
425
|
+
* infers the schema, and its hooks are typed from it.
|
|
426
|
+
*/
|
|
427
|
+
type AnyCustomNodeDefinition = CustomNodeDefinition;
|
|
428
|
+
/**
|
|
429
|
+
* A chip activation: identity + attrs, plus where it sits.
|
|
430
|
+
*
|
|
431
|
+
* `attrs` are the definition's OWN shape — the raw tag decode has already been
|
|
432
|
+
* through `fromDocx`, exactly as the review derivation runs it, so every
|
|
433
|
+
* surface (click, hover, edit, cards) sees one attrs vocabulary. `text` and
|
|
434
|
+
* `nodeId` are present when the surface could resolve them (a registered
|
|
435
|
+
* review module resolves both).
|
|
436
|
+
*/
|
|
437
|
+
interface ActivatedCustomNode {
|
|
438
|
+
readonly name: string;
|
|
439
|
+
readonly attrs: Readonly<Record<string, string>>;
|
|
440
|
+
readonly tag: string;
|
|
441
|
+
/** Viewport-relative rect of the chip's boundary, for anchoring host UI. */
|
|
442
|
+
readonly rect: DOMRect;
|
|
443
|
+
/** The SDT node's canonical id — the address `removeContentControl` takes. */
|
|
444
|
+
readonly nodeId?: string;
|
|
445
|
+
/** The node's literal content text, when resolvable. */
|
|
446
|
+
readonly text?: string;
|
|
447
|
+
/**
|
|
448
|
+
* The node's payload, when the surface could resolve one.
|
|
449
|
+
*
|
|
450
|
+
* Present only where the review derivation has already run — a chip's own click and hover
|
|
451
|
+
* resolve through the review item, which is what carries the payload. Undefined otherwise,
|
|
452
|
+
* and undefined for a node whose payload failed its schema.
|
|
453
|
+
*/
|
|
454
|
+
readonly data?: unknown;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Whether an opaque registry value is a custom-node definition.
|
|
458
|
+
*
|
|
459
|
+
* The engine carries registered definitions as unknowns (`getCustomNodeDefinitions`), so
|
|
460
|
+
* every pro surface that reads them back narrows through this ONE guard.
|
|
461
|
+
*/
|
|
462
|
+
declare function isCustomNodeDefinition(candidate: unknown): candidate is AnyCustomNodeDefinition;
|
|
463
|
+
/** Validate and freeze a definition. Throws on a shape mistake — author error, not file input. */
|
|
464
|
+
declare function defineCustomNode<Schema extends StandardSchemaV1 | undefined = undefined>(definition: CustomNodeDefinition<Schema>): CustomNode<Schema>;
|
|
465
|
+
/**
|
|
466
|
+
* How {@link customNodesModule} is configured.
|
|
467
|
+
*
|
|
468
|
+
* @public
|
|
469
|
+
*/
|
|
470
|
+
interface CustomNodesModuleOptions extends ProLicenseOptions {
|
|
471
|
+
/** The definitions this editor recognizes. A tag prefix no definition claims stays literal. */
|
|
472
|
+
readonly nodes: readonly AnyCustomNodeDefinition[];
|
|
473
|
+
/**
|
|
474
|
+
* Told about a document, never about a bug: a payload that failed its schema, so far.
|
|
475
|
+
*
|
|
476
|
+
* A payload comes from a file the sender wrote, so a mismatch is an ordinary property of an
|
|
477
|
+
* ordinary document. The node still renders, without its `data`; this is how an integrator
|
|
478
|
+
* finds out rather than wondering why one chip's dialog is empty.
|
|
479
|
+
*/
|
|
480
|
+
readonly onDiagnostic?: (diagnostic: CustomNodeDiagnostic) => void;
|
|
481
|
+
}
|
|
482
|
+
/** Register custom node definitions with `createDocxEditor({ modules })`. */
|
|
483
|
+
declare function customNodesModule(options: CustomNodesModuleOptions): EditorModule;
|
|
484
|
+
/**
|
|
485
|
+
* Every recognized custom node in one story, in document order.
|
|
486
|
+
*
|
|
487
|
+
* Tag-prefix keyed, exactly as the change specifies: an inline SDT whose tag
|
|
488
|
+
* decodes to a registered `<prefix>:<name>` pair is offered to that
|
|
489
|
+
* definition's `fromDocx`; everything else — foreign tags, unregistered
|
|
490
|
+
* prefixes, a `fromDocx` veto — stays a literal SDT.
|
|
491
|
+
*/
|
|
492
|
+
interface RecognizeCustomNodesOptions {
|
|
493
|
+
/** The payload each control binds, from `customNodePayloadsByControl`. */
|
|
494
|
+
readonly payloads?: ReadonlyMap<string, CustomNodePayloadSource>;
|
|
495
|
+
/** Told about a node whose payload could not be read. Omitted, nothing is reported. */
|
|
496
|
+
readonly onDiagnostic?: (diagnostic: CustomNodeDiagnostic) => void;
|
|
497
|
+
}
|
|
498
|
+
declare function recognizeCustomNodes(part: OoxmlPart, definitions: readonly AnyCustomNodeDefinition[], options?: RecognizeCustomNodesOptions): RecognizedCustomNode[];
|
|
499
|
+
|
|
500
|
+
export { type AnyCustomNodeDefinition as A, type CustomNodeDiagnostic as C, type InferSchemaInput as I, MAX_CUSTOM_NODE_DATA_LENGTH as M, type ProLicenseOptions as P, type RecognizedCustomNode as R, type StandardSchemaV1 as S, type CustomNodeDefinition as a, type ActivatedCustomNode as b, type CustomNode as c, type CustomNodeDataRejection as d, type CustomNodeDataResult as e, type CustomNodePayloadSource as f, type CustomNodesModuleOptions as g, type InferSchemaOutput as h, type RecognizeCustomNodesOptions as i, type ReviewModuleOptions as j, type StandardSchemaIssue as k, type StandardSchemaResult as l, customNodesModule as m, defineCustomNode as n, isCustomNodeDefinition as o, parseCustomNodeData as p, reviewModule as q, recognizeCustomNodes as r, serializeCustomNodeData as s };
|