fountainjs-editor 0.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/LICENSE +21 -0
- package/README.md +460 -0
- package/dist/fountainjs-react.cjs +1 -0
- package/dist/fountainjs-react.js +8 -0
- package/dist/fountainjs.cjs +1 -0
- package/dist/fountainjs.js +219 -0
- package/dist/index-1c508d95.js +1172 -0
- package/dist/index-cafd8e26.cjs +40 -0
- package/package.json +76 -0
- package/src/core/editor.ts +15 -0
- package/src/core/index.ts +6 -0
- package/src/core/plugin.ts +4 -0
- package/src/core/schema/index.ts +5 -0
- package/src/core/schema/mark-spec.ts +5 -0
- package/src/core/schema/mark.ts +7 -0
- package/src/core/schema/node-spec.ts +12 -0
- package/src/core/schema/node.ts +12 -0
- package/src/core/schema/schema.ts +17 -0
- package/src/core/selection.ts +17 -0
- package/src/core/state.ts +13 -0
- package/src/core/transaction/add-mark-step.ts +19 -0
- package/src/core/transaction/index.ts +9 -0
- package/src/core/transaction/insert-text-step.ts +18 -0
- package/src/core/transaction/remove-mark-step.ts +17 -0
- package/src/core/transaction/replace-step.ts +6 -0
- package/src/core/transaction/replace-text-step.ts +33 -0
- package/src/core/transaction/set-node-attrs-step.ts +30 -0
- package/src/core/transaction/step.ts +2 -0
- package/src/core/transaction/transaction.ts +8 -0
- package/src/core/transaction/transform.ts +23 -0
- package/src/extensions/index.ts +64 -0
- package/src/extensions/marks/em.ts +2 -0
- package/src/extensions/marks/strong.ts +2 -0
- package/src/extensions/nodes/bullet-list.ts +9 -0
- package/src/extensions/nodes/doc.ts +2 -0
- package/src/extensions/nodes/figcaption.ts +5 -0
- package/src/extensions/nodes/heading.ts +7 -0
- package/src/extensions/nodes/image-super-view.ts +90 -0
- package/src/extensions/nodes/image-super.ts +7 -0
- package/src/extensions/nodes/list-item.ts +9 -0
- package/src/extensions/nodes/paragraph.ts +2 -0
- package/src/extensions/nodes/table-cell.ts +5 -0
- package/src/extensions/nodes/table-row.ts +2 -0
- package/src/extensions/nodes/table.ts +5 -0
- package/src/extensions/nodes/text.ts +2 -0
- package/src/extensions/plugins/history.ts +12 -0
- package/src/extensions/plugins/markdown-shortcuts.ts +52 -0
- package/src/index.ts +12 -0
- package/src/react/FountainEditor.tsx +19 -0
- package/src/react/Navigator.tsx +36 -0
- package/src/react/index.ts +4 -0
- package/src/react/useFountain.ts +10 -0
- package/src/react/useNavigatorState.ts +41 -0
- package/src/view/dom-renderer.ts +77 -0
- package/src/view/index.ts +2 -0
- package/src/view/input.ts +74 -0
- package/src/view/node-view.ts +9 -0
- package/src/view/selection-handler.ts +76 -0
- package/src/view/view.ts +290 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";var Pt=Object.defineProperty;var Dt=(u,t,r)=>t in u?Pt(u,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):u[t]=r;var h=(u,t,r)=>(Dt(u,typeof t!="symbol"?t+"":t,r),r);const j=require("react");class b{constructor(t,r,n=[],i,d=[]){h(this,"type");h(this,"attrs");h(this,"content");h(this,"text");h(this,"marks");this.type=t,this.attrs=r,this.content=n,this.text=i,this.marks=d}get isText(){return this.type.name==="text"}withText(t){if(!this.isText)throw new Error("Cannot call withText on a non-text node.");return new b(this.type,this.attrs,[],t,this.marks)}}class Ne{constructor(t,r){h(this,"name");h(this,"spec");h(this,"isBlock");h(this,"isInline");this.name=t,this.spec=r,this.isInline=!!r.inline,this.isBlock=!this.isInline}}class Me{constructor(t,r){h(this,"name");h(this,"spec");this.name=t,this.spec=r}}class Ae{constructor(t){h(this,"spec");h(this,"nodes");h(this,"marks");this.spec=t,this.nodes=this.compileNodes(t.nodes),this.marks=this.compileMarks(t.marks||{})}compileNodes(t){const r={};for(const n in t)r[n]=new Ne(n,t[n]);return r}compileMarks(t){const r={};for(const n in t)r[n]=new Me(n,t[n]);return r}}class W{}class Fe extends W{constructor(t,r,n){super(),this.from=t,this.to=r,this.content=n}apply(t){if(this.from>this.to||this.from>t.content.length||this.to>t.content.length)throw new Error("ReplaceStep apply error: Invalid range");const r=[...t.content.slice(0,this.from),...this.content,...t.content.slice(this.to)];return new b(t.type,t.attrs,r)}}class Ie extends W{constructor(t,r,n,i){super(),this.path=t,this.from=r,this.to=n,this.text=i}apply(t){let r=t,n=[];for(const f of this.path)n.push(r),r=r.content[f];if(!r||!r.isText)throw new Error("Target for ReplaceTextStep is not a text node.");const i=r.text||"",d=i.slice(0,this.from)+this.text+i.slice(this.to);let o=r.withText(d);for(let f=n.length-1;f>=0;f--){const w=n[f],y=[...w.content];y[this.path[f]]=o,o=new b(w.type,w.attrs,y,w.text,w.marks)}return o}}class We extends W{constructor(t,r){super(),this.path=t,this.mark=r}apply(t){let r=t,n=[];for(const o of this.path)n.push(r),r=r.content[o];if(!r||!r.isText)return t;const i=[this.mark,...r.marks.filter(o=>o.type!==this.mark.type)];let c=new b(r.type,r.attrs,[],r.text,i);for(let o=n.length-1;o>=0;o--){const f=n[o],w=[...f.content];w[this.path[o]]=c,c=new b(f.type,f.attrs,w,f.text,f.marks)}return c}}class $e extends W{constructor(t,r,n){super(),this.from=t,this.to=r,this.markType=n}apply(t){const r=t.content.map((n,i)=>{if(i>=this.from&&i<this.to&&n.type.name==="paragraph"){const d=n.content.map(c=>{if(c.isText){const o=c.marks.filter(f=>f.type!==this.markType);return new b(c.type,c.attrs,[],c.text,o)}return c});return new b(n.type,n.attrs,d)}return n});return new b(t.type,t.attrs,r)}}class Le extends W{constructor(t,r){super(),this.path=t,this.attrs=r}apply(t){let r=t,n=[];for(const o of this.path)n.push(r),r=r.content[o];if(!r)throw new Error("No node found at path");const i={...r.attrs,...this.attrs};let c=new b(r.type,i,r.content,r.text,r.marks);for(let o=n.length-1;o>=0;o--){const f=n[o],w=[...f.content];w[this.path[o]]=c,c=new b(f.type,f.attrs,w,f.text,f.marks)}return c}}class Ye{constructor(t){h(this,"originalDoc");h(this,"doc");h(this,"steps");this.originalDoc=t,this.doc=t,this.steps=[]}step(t){const r=t.apply(this.doc);return r&&(this.doc=r,this.steps.push(t)),this}replace(t,r,n=[]){return this.step(new Fe(t,r,n))}replaceText(t,r,n,i){return this.step(new Ie(t,r,n,i))}addMark(t,r){return this.step(new We(t,r))}removeMark(t,r,n){return this.step(new $e(t,r,n))}setNodeAttrs(t,r){return this.step(new Le(t,r))}}class N{constructor(t,r,n){this.path=t,this.from=r,this.to=n}static createCursor(t,r){return new N(t,r,r)}get isCollapsed(){return this.from===this.to}}class Ve extends Ye{constructor(r){super(r);h(this,"selection");h(this,"selectionSet",!1);this.selection=N.createCursor([],0)}setSelection(r){return this.selection=r,this.selectionSet=!0,this}}class U{constructor(t){h(this,"schema");h(this,"doc");h(this,"selection");h(this,"plugins");h(this,"pluginStates");this.schema=t.schema,this.doc=t.doc||this.createDefaultDoc(t.schema),this.selection=t.selection||N.createCursor([0,0],19),this.plugins=t.plugins||[],t.pluginStates?this.pluginStates=t.pluginStates:this.pluginStates=this.plugins.map(r=>{var n;return(n=r.spec.state)==null?void 0:n.init({},this)})}static create(t){return new U(t)}apply(t){const r=this.plugins.map((d,c)=>{const o=d.spec.state;return o?o.apply(t,this.pluginStates[c],this):this.pluginStates[c]}),n=t.doc,i=t.selectionSet?t.selection:this.selection;return new U({schema:this.schema,doc:n,selection:i,plugins:this.plugins,pluginStates:r})}createTransaction(){return new Ve(this.doc)}createDefaultDoc(t){const r=t.nodes.doc,n=t.nodes.paragraph,i=t.nodes.text;if(!r||!n||!i)throw new Error("Schema is missing core nodes.");const d=new b(i,{},[],"Start typing here..."),c=new b(n,{},[d]);return new b(r,{},[c])}}class Ke{constructor(t){h(this,"_state");h(this,"subscribers",new Set);this._state=t.state}get state(){return this._state}createTransaction(){return this.state.createTransaction()}dispatch(t){const r=this._state.apply(t);r!==this._state&&(this._state=r,this.subscribers.forEach(n=>n(r)))}subscribe(t){return this.subscribers.add(t),()=>this.subscribers.delete(t)}}function Ue(u){const t=new Ae(u.schema),r=u.plugins||[],n=u.state||U.create({schema:t,plugins:r});return new Ke({state:n})}class qe{constructor(t,r){h(this,"editor");h(this,"dom");h(this,"isDestroyed",!1);h(this,"isReconciling",!1);h(this,"unsubscribe");h(this,"nodeToDOM",new WeakMap);h(this,"domToPath",new WeakMap);h(this,"handleInput",()=>{this.isReconciling||this.reconcile()});h(this,"handleKeyDown",t=>{if((t.ctrlKey||t.metaKey)&&t.key==="b"){t.preventDefault(),this.toggleMark("strong");return}if((t.ctrlKey||t.metaKey)&&t.key==="i"){t.preventDefault(),this.toggleMark("em");return}if((t.ctrlKey||t.metaKey)&&t.key==="z"&&!t.shiftKey){t.preventDefault();return}});h(this,"handlePaste",t=>{var n;t.preventDefault();const r=(n=t.clipboardData)==null?void 0:n.getData("text/plain");r&&document.execCommand("insertText",!1,r)});h(this,"reconcile",()=>{if(!this.isReconciling){this.isReconciling=!0;try{const{state:t}=this.editor,r=window.getSelection();if(!r||!r.anchorNode){this.isReconciling=!1;return}const n=this.extractContent(this.dom);if(n.length>0){const i=t.createTransaction().replace(0,t.doc.content.length,n);if(r.anchorNode){const d=r.anchorOffset;i.setSelection(N.createCursor([0,0],d))}this.editor.dispatch(i)}}finally{queueMicrotask(()=>{this.isReconciling=!1})}}});h(this,"onStateChange",t=>{this.isDestroyed||this.isReconciling||this.render(t)});this.editor=r,this.dom=document.createElement("div"),this.dom.setAttribute("role","textbox"),this.dom.setAttribute("aria-label","Editor"),this.dom.contentEditable="true",this.dom.style.cssText=`
|
|
2
|
+
padding: 12px;
|
|
3
|
+
min-height: 200px;
|
|
4
|
+
outline: none;
|
|
5
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
6
|
+
font-size: 16px;
|
|
7
|
+
line-height: 1.6;
|
|
8
|
+
color: #333;
|
|
9
|
+
`,t.appendChild(this.dom),this.dom.addEventListener("input",this.handleInput),this.dom.addEventListener("keydown",this.handleKeyDown),this.dom.addEventListener("paste",this.handlePaste),this.unsubscribe=this.editor.subscribe(this.onStateChange),this.render(this.editor.state)}toggleMark(t){const{state:r}=this.editor,n=window.getSelection();if(!n||n.isCollapsed)return;const{anchorNode:i,focusNode:d,anchorOffset:c,focusOffset:o}=n;if(!i||!d)return;const f=n.toString();if(r.schema.marks[t]){const y=document.createElement("span");t==="strong"&&(y.style.fontWeight="bold"),t==="em"&&(y.style.fontStyle="italic"),y.textContent=f;try{const T=n.getRangeAt(0);T.deleteContents(),T.insertNode(y)}catch{t==="strong"&&document.execCommand("bold"),t==="em"&&document.execCommand("italic")}}queueMicrotask(()=>this.reconcile())}extractContent(t){const r=[];for(let n=0;n<t.childNodes.length;n++){const i=t.childNodes[n];if(i.nodeType===3){const d=i.textContent||"";if(d.trim()){const c=new b(this.editor.state.schema.nodes.text,{},[],d),o=new b(this.editor.state.schema.nodes.paragraph,{},[c]);r.push(o)}}else if(i.nodeType===1){const d=i,c=d.tagName.toLowerCase();if(c==="p"||c==="div"){const o=d.textContent||"",f=new b(this.editor.state.schema.nodes.text,{},[],o),w=new b(this.editor.state.schema.nodes.paragraph,{},[f]);r.push(w)}else if(c==="h1"||c==="h2"||c==="h3"){const o=parseInt(c[1]),f=d.textContent||"",w=new b(this.editor.state.schema.nodes.text,{},[],f),y=new b(this.editor.state.schema.nodes.heading,{level:o},[w]);r.push(y)}}}return r.length>0?r:[new b(this.editor.state.schema.nodes.paragraph,{},[new b(this.editor.state.schema.nodes.text,{},[],"")])]}render(t){this.nodeToDOM=new WeakMap,this.domToPath=new WeakMap;const r=this.renderNode(t.doc,[]);if(r.childNodes.length>0){this.dom.innerHTML="";for(let n=0;n<r.childNodes.length;n++)this.dom.appendChild(r.childNodes[n].cloneNode(!0))}queueMicrotask(()=>this.restoreSelection(t.selection))}renderNode(t,r){const n=document.createElement("div");if(t.type&&t.type.name==="text")n.textContent=t.text||"";else{const d=this.getTagForNode(t),c=document.createElement(d);if(t.attrs&&t.attrs.level&&c.setAttribute("data-level",t.attrs.level),t.attrs&&t.attrs.src){const o=document.createElement("img");o.src=t.attrs.src,o.style.maxWidth="100%",o.style.height="auto",c.appendChild(o)}if(t.content)for(let o=0;o<t.content.length;o++){const f=t.content[o],w=[...r,o],y=this.renderNode(f,w);for(let T=0;T<y.childNodes.length;T++)c.appendChild(y.childNodes[T].cloneNode(!0))}else t.text&&(c.textContent=t.text);this.nodeToDOM.set(t,c),n.appendChild(c)}return n}getTagForNode(t){var i;const r=t.type.name;return{heading:`h${((i=t.attrs)==null?void 0:i.level)||1}`,paragraph:"p",bullet_list:"ul",list_item:"li",table:"table",table_row:"tr",table_cell:"td",image_super:"figure",figcaption:"figcaption"}[r]||"div"}restoreSelection(t){var n;const r=window.getSelection();if(r)try{const i=this.dom.querySelector("p, h1, h2, h3, h4, h5, h6");if(i!=null&&i.firstChild){const d=document.createRange(),c=Math.min(t.to,(((n=i.firstChild.textContent)==null?void 0:n.length)??0)-1);d.setStart(i.firstChild,Math.max(0,c)),d.collapse(!0),r.removeAllRanges(),r.addRange(d)}}catch{}}execCommand(t,r){return document.execCommand(t,!1,r)}destroy(){this.isDestroyed||(this.isDestroyed=!0,this.unsubscribe(),this.dom.removeEventListener("input",this.handleInput),this.dom.removeEventListener("keydown",this.handleKeyDown),this.dom.removeEventListener("paste",this.handlePaste),this.dom.remove())}}function jt(u){const[t]=j.useState(()=>u?Ue(u):null);return t}var ae={exports:{}},V={};/**
|
|
10
|
+
* @license React
|
|
11
|
+
* react-jsx-runtime.production.min.js
|
|
12
|
+
*
|
|
13
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
14
|
+
*
|
|
15
|
+
* This source code is licensed under the MIT license found in the
|
|
16
|
+
* LICENSE file in the root directory of this source tree.
|
|
17
|
+
*/var De;function Nt(){if(De)return V;De=1;var u=j,t=Symbol.for("react.element"),r=Symbol.for("react.fragment"),n=Object.prototype.hasOwnProperty,i=u.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,d={key:!0,ref:!0,__self:!0,__source:!0};function c(o,f,w){var y,T={},O=null,q=null;w!==void 0&&(O=""+w),f.key!==void 0&&(O=""+f.key),f.ref!==void 0&&(q=f.ref);for(y in f)n.call(f,y)&&!d.hasOwnProperty(y)&&(T[y]=f[y]);if(o&&o.defaultProps)for(y in f=o.defaultProps,f)T[y]===void 0&&(T[y]=f[y]);return{$$typeof:t,type:o,key:O,ref:q,props:T,_owner:i.current}}return V.Fragment=r,V.jsx=c,V.jsxs=c,V}var K={};/**
|
|
18
|
+
* @license React
|
|
19
|
+
* react-jsx-runtime.development.js
|
|
20
|
+
*
|
|
21
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
22
|
+
*
|
|
23
|
+
* This source code is licensed under the MIT license found in the
|
|
24
|
+
* LICENSE file in the root directory of this source tree.
|
|
25
|
+
*/var je;function Mt(){return je||(je=1,process.env.NODE_ENV!=="production"&&function(){var u=j,t=Symbol.for("react.element"),r=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),i=Symbol.for("react.strict_mode"),d=Symbol.for("react.profiler"),c=Symbol.for("react.provider"),o=Symbol.for("react.context"),f=Symbol.for("react.forward_ref"),w=Symbol.for("react.suspense"),y=Symbol.for("react.suspense_list"),T=Symbol.for("react.memo"),O=Symbol.for("react.lazy"),q=Symbol.for("react.offscreen"),ie=Symbol.iterator,Je="@@iterator";function ze(e){if(e===null||typeof e!="object")return null;var s=ie&&e[ie]||e[Je];return typeof s=="function"?s:null}var M=u.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;function R(e){{for(var s=arguments.length,a=new Array(s>1?s-1:0),l=1;l<s;l++)a[l-1]=arguments[l];Ge("error",e,a)}}function Ge(e,s,a){{var l=M.ReactDebugCurrentFrame,m=l.getStackAddendum();m!==""&&(s+="%s",a=a.concat([m]));var g=a.map(function(v){return String(v)});g.unshift("Warning: "+s),Function.prototype.apply.call(console[e],console,g)}}var He=!1,Xe=!1,Ze=!1,Qe=!1,et=!1,oe;oe=Symbol.for("react.module.reference");function tt(e){return!!(typeof e=="string"||typeof e=="function"||e===n||e===d||et||e===i||e===w||e===y||Qe||e===q||He||Xe||Ze||typeof e=="object"&&e!==null&&(e.$$typeof===O||e.$$typeof===T||e.$$typeof===c||e.$$typeof===o||e.$$typeof===f||e.$$typeof===oe||e.getModuleId!==void 0))}function rt(e,s,a){var l=e.displayName;if(l)return l;var m=s.displayName||s.name||"";return m!==""?a+"("+m+")":a}function ce(e){return e.displayName||"Context"}function k(e){if(e==null)return null;if(typeof e.tag=="number"&&R("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case n:return"Fragment";case r:return"Portal";case d:return"Profiler";case i:return"StrictMode";case w:return"Suspense";case y:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case o:var s=e;return ce(s)+".Consumer";case c:var a=e;return ce(a._context)+".Provider";case f:return rt(e,e.render,"ForwardRef");case T:var l=e.displayName||null;return l!==null?l:k(e.type)||"Memo";case O:{var m=e,g=m._payload,v=m._init;try{return k(v(g))}catch{return null}}}return null}var P=Object.assign,$=0,le,ue,fe,de,he,pe,ve;function me(){}me.__reactDisabledLog=!0;function nt(){{if($===0){le=console.log,ue=console.info,fe=console.warn,de=console.error,he=console.group,pe=console.groupCollapsed,ve=console.groupEnd;var e={configurable:!0,enumerable:!0,value:me,writable:!0};Object.defineProperties(console,{info:e,log:e,warn:e,error:e,group:e,groupCollapsed:e,groupEnd:e})}$++}}function st(){{if($--,$===0){var e={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:P({},e,{value:le}),info:P({},e,{value:ue}),warn:P({},e,{value:fe}),error:P({},e,{value:de}),group:P({},e,{value:he}),groupCollapsed:P({},e,{value:pe}),groupEnd:P({},e,{value:ve})})}$<0&&R("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var H=M.ReactCurrentDispatcher,X;function B(e,s,a){{if(X===void 0)try{throw Error()}catch(m){var l=m.stack.trim().match(/\n( *(at )?)/);X=l&&l[1]||""}return`
|
|
26
|
+
`+X+e}}var Z=!1,J;{var at=typeof WeakMap=="function"?WeakMap:Map;J=new at}function ge(e,s){if(!e||Z)return"";{var a=J.get(e);if(a!==void 0)return a}var l;Z=!0;var m=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var g;g=H.current,H.current=null,nt();try{if(s){var v=function(){throw Error()};if(Object.defineProperty(v.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(v,[])}catch(_){l=_}Reflect.construct(e,[],v)}else{try{v.call()}catch(_){l=_}e.call(v.prototype)}}else{try{throw Error()}catch(_){l=_}e()}}catch(_){if(_&&l&&typeof _.stack=="string"){for(var p=_.stack.split(`
|
|
27
|
+
`),S=l.stack.split(`
|
|
28
|
+
`),x=p.length-1,E=S.length-1;x>=1&&E>=0&&p[x]!==S[E];)E--;for(;x>=1&&E>=0;x--,E--)if(p[x]!==S[E]){if(x!==1||E!==1)do if(x--,E--,E<0||p[x]!==S[E]){var C=`
|
|
29
|
+
`+p[x].replace(" at new "," at ");return e.displayName&&C.includes("<anonymous>")&&(C=C.replace("<anonymous>",e.displayName)),typeof e=="function"&&J.set(e,C),C}while(x>=1&&E>=0);break}}}finally{Z=!1,H.current=g,st(),Error.prepareStackTrace=m}var F=e?e.displayName||e.name:"",D=F?B(F):"";return typeof e=="function"&&J.set(e,D),D}function it(e,s,a){return ge(e,!1)}function ot(e){var s=e.prototype;return!!(s&&s.isReactComponent)}function z(e,s,a){if(e==null)return"";if(typeof e=="function")return ge(e,ot(e));if(typeof e=="string")return B(e);switch(e){case w:return B("Suspense");case y:return B("SuspenseList")}if(typeof e=="object")switch(e.$$typeof){case f:return it(e.render);case T:return z(e.type,s,a);case O:{var l=e,m=l._payload,g=l._init;try{return z(g(m),s,a)}catch{}}}return""}var L=Object.prototype.hasOwnProperty,ye={},we=M.ReactDebugCurrentFrame;function G(e){if(e){var s=e._owner,a=z(e.type,e._source,s?s.type:null);we.setExtraStackFrame(a)}else we.setExtraStackFrame(null)}function ct(e,s,a,l,m){{var g=Function.call.bind(L);for(var v in e)if(g(e,v)){var p=void 0;try{if(typeof e[v]!="function"){var S=Error((l||"React class")+": "+a+" type `"+v+"` is invalid; it must be a function, usually from the `prop-types` package, but received `"+typeof e[v]+"`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");throw S.name="Invariant Violation",S}p=e[v](s,v,l,a,null,"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED")}catch(x){p=x}p&&!(p instanceof Error)&&(G(m),R("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).",l||"React class",a,v,typeof p),G(null)),p instanceof Error&&!(p.message in ye)&&(ye[p.message]=!0,G(m),R("Failed %s type: %s",a,p.message),G(null))}}}var lt=Array.isArray;function Q(e){return lt(e)}function ut(e){{var s=typeof Symbol=="function"&&Symbol.toStringTag,a=s&&e[Symbol.toStringTag]||e.constructor.name||"Object";return a}}function ft(e){try{return be(e),!1}catch{return!0}}function be(e){return""+e}function xe(e){if(ft(e))return R("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.",ut(e)),be(e)}var Y=M.ReactCurrentOwner,dt={key:!0,ref:!0,__self:!0,__source:!0},Ee,Te,ee;ee={};function ht(e){if(L.call(e,"ref")){var s=Object.getOwnPropertyDescriptor(e,"ref").get;if(s&&s.isReactWarning)return!1}return e.ref!==void 0}function pt(e){if(L.call(e,"key")){var s=Object.getOwnPropertyDescriptor(e,"key").get;if(s&&s.isReactWarning)return!1}return e.key!==void 0}function vt(e,s){if(typeof e.ref=="string"&&Y.current&&s&&Y.current.stateNode!==s){var a=k(Y.current.type);ee[a]||(R('Component "%s" contains the string ref "%s". Support for string refs will be removed in a future major release. This case cannot be automatically converted to an arrow function. We ask you to manually fix this case by using useRef() or createRef() instead. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref',k(Y.current.type),e.ref),ee[a]=!0)}}function mt(e,s){{var a=function(){Ee||(Ee=!0,R("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)",s))};a.isReactWarning=!0,Object.defineProperty(e,"key",{get:a,configurable:!0})}}function gt(e,s){{var a=function(){Te||(Te=!0,R("%s: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)",s))};a.isReactWarning=!0,Object.defineProperty(e,"ref",{get:a,configurable:!0})}}var yt=function(e,s,a,l,m,g,v){var p={$$typeof:t,type:e,key:s,ref:a,props:v,_owner:g};return p._store={},Object.defineProperty(p._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:!1}),Object.defineProperty(p,"_self",{configurable:!1,enumerable:!1,writable:!1,value:l}),Object.defineProperty(p,"_source",{configurable:!1,enumerable:!1,writable:!1,value:m}),Object.freeze&&(Object.freeze(p.props),Object.freeze(p)),p};function wt(e,s,a,l,m){{var g,v={},p=null,S=null;a!==void 0&&(xe(a),p=""+a),pt(s)&&(xe(s.key),p=""+s.key),ht(s)&&(S=s.ref,vt(s,m));for(g in s)L.call(s,g)&&!dt.hasOwnProperty(g)&&(v[g]=s[g]);if(e&&e.defaultProps){var x=e.defaultProps;for(g in x)v[g]===void 0&&(v[g]=x[g])}if(p||S){var E=typeof e=="function"?e.displayName||e.name||"Unknown":e;p&&mt(v,E),S&>(v,E)}return yt(e,p,S,m,l,Y.current,v)}}var te=M.ReactCurrentOwner,Re=M.ReactDebugCurrentFrame;function A(e){if(e){var s=e._owner,a=z(e.type,e._source,s?s.type:null);Re.setExtraStackFrame(a)}else Re.setExtraStackFrame(null)}var re;re=!1;function ne(e){return typeof e=="object"&&e!==null&&e.$$typeof===t}function Se(){{if(te.current){var e=k(te.current.type);if(e)return`
|
|
30
|
+
|
|
31
|
+
Check the render method of \``+e+"`."}return""}}function bt(e){{if(e!==void 0){var s=e.fileName.replace(/^.*[\\\/]/,""),a=e.lineNumber;return`
|
|
32
|
+
|
|
33
|
+
Check your code at `+s+":"+a+"."}return""}}var _e={};function xt(e){{var s=Se();if(!s){var a=typeof e=="string"?e:e.displayName||e.name;a&&(s=`
|
|
34
|
+
|
|
35
|
+
Check the top-level render call using <`+a+">.")}return s}}function Ce(e,s){{if(!e._store||e._store.validated||e.key!=null)return;e._store.validated=!0;var a=xt(s);if(_e[a])return;_e[a]=!0;var l="";e&&e._owner&&e._owner!==te.current&&(l=" It was passed a child from "+k(e._owner.type)+"."),A(e),R('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.',a,l),A(null)}}function ke(e,s){{if(typeof e!="object")return;if(Q(e))for(var a=0;a<e.length;a++){var l=e[a];ne(l)&&Ce(l,s)}else if(ne(e))e._store&&(e._store.validated=!0);else if(e){var m=ze(e);if(typeof m=="function"&&m!==e.entries)for(var g=m.call(e),v;!(v=g.next()).done;)ne(v.value)&&Ce(v.value,s)}}}function Et(e){{var s=e.type;if(s==null||typeof s=="string")return;var a;if(typeof s=="function")a=s.propTypes;else if(typeof s=="object"&&(s.$$typeof===f||s.$$typeof===T))a=s.propTypes;else return;if(a){var l=k(s);ct(a,e.props,"prop",l,e)}else if(s.PropTypes!==void 0&&!re){re=!0;var m=k(s);R("Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?",m||"Unknown")}typeof s.getDefaultProps=="function"&&!s.getDefaultProps.isReactClassApproved&&R("getDefaultProps is only used on classic React.createClass definitions. Use a static property named `defaultProps` instead.")}}function Tt(e){{for(var s=Object.keys(e.props),a=0;a<s.length;a++){var l=s[a];if(l!=="children"&&l!=="key"){A(e),R("Invalid prop `%s` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.",l),A(null);break}}e.ref!==null&&(A(e),R("Invalid attribute `ref` supplied to `React.Fragment`."),A(null))}}var Oe={};function Pe(e,s,a,l,m,g){{var v=tt(e);if(!v){var p="";(e===void 0||typeof e=="object"&&e!==null&&Object.keys(e).length===0)&&(p+=" You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.");var S=bt(m);S?p+=S:p+=Se();var x;e===null?x="null":Q(e)?x="array":e!==void 0&&e.$$typeof===t?(x="<"+(k(e.type)||"Unknown")+" />",p=" Did you accidentally export a JSX literal instead of a component?"):x=typeof e,R("React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s",x,p)}var E=wt(e,s,a,m,g);if(E==null)return E;if(v){var C=s.children;if(C!==void 0)if(l)if(Q(C)){for(var F=0;F<C.length;F++)ke(C[F],e);Object.freeze&&Object.freeze(C)}else R("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else ke(C,e)}if(L.call(s,"key")){var D=k(e),_=Object.keys(s).filter(function(Ot){return Ot!=="key"}),se=_.length>0?"{key: someKey, "+_.join(": ..., ")+": ...}":"{key: someKey}";if(!Oe[D+se]){var kt=_.length>0?"{"+_.join(": ..., ")+": ...}":"{}";R(`A props object containing a "key" prop is being spread into JSX:
|
|
36
|
+
let props = %s;
|
|
37
|
+
<%s {...props} />
|
|
38
|
+
React keys must be passed directly to JSX without using spread:
|
|
39
|
+
let props = %s;
|
|
40
|
+
<%s key={someKey} {...props} />`,se,D,kt,D),Oe[D+se]=!0}}return e===n?Tt(E):Et(E),E}}function Rt(e,s,a){return Pe(e,s,a,!0)}function St(e,s,a){return Pe(e,s,a,!1)}var _t=St,Ct=Rt;K.Fragment=n,K.jsx=_t,K.jsxs=Ct}()),K}process.env.NODE_ENV==="production"?ae.exports=Nt():ae.exports=Mt();var I=ae.exports;const At=({editor:u})=>{const t=j.useRef(null);return j.useEffect(()=>{if(!u||!t.current)return;const r=new qe(t.current,u);return()=>{r.destroy()}},[u]),I.jsx("div",{ref:t})};function Ft(u){const t=[];function r(n,i){n.type.name==="heading"&&t.push({id:`${i.join("-")}-${n.attrs.level}`,level:n.attrs.level,text:n.content.map(d=>d.text).join("")||"Untitled Heading",path:i}),n.content.forEach((d,c)=>{r(d,[...i,c])})}return r(u,[]),t}function Be(u){const[t,r]=j.useState([]);return j.useEffect(()=>{if(!u)return;const n=()=>{const d=Ft(u.state.doc);r(d)};n();const i=u.subscribe(n);return()=>i()},[u]),t}const It=({editor:u})=>{const t=Be(u);if(!u)return null;const r=n=>{const i=N.createCursor(n,0),d=u.createTransaction().setSelection(i);u.dispatch(d)};return I.jsxs("div",{style:{padding:"1rem",border:"1px solid #eee",background:"#fcfcfc"},children:[I.jsx("h3",{style:{marginTop:0},children:"Navigator"}),t.length===0&&I.jsx("p",{style:{color:"#999"},children:"No headings yet."}),I.jsx("ul",{children:t.map(n=>I.jsx("li",{onClick:()=>r(n.path),style:{listStyle:"none",paddingLeft:`${(n.level-1)*20}px`,cursor:"pointer",marginBottom:"0.5rem"},children:n.text},n.id))})]})};exports.AddMarkStep=We;exports.Editor=Ke;exports.EditorState=U;exports.EditorView=qe;exports.FountainEditor=At;exports.MarkType=Me;exports.Navigator=It;exports.Node=b;exports.NodeType=Ne;exports.RemoveMarkStep=$e;exports.ReplaceStep=Fe;exports.ReplaceTextStep=Ie;exports.Schema=Ae;exports.Selection=N;exports.SetNodeAttrsStep=Le;exports.Step=W;exports.Transaction=Ve;exports.Transform=Ye;exports.createEditor=Ue;exports.useFountain=jt;exports.useNavigatorState=Be;
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fountainjs-editor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A modular, extensible rich text editor library for React and other frameworks",
|
|
5
|
+
"author": "Your Name <your.email@example.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/fountainjs.cjs",
|
|
9
|
+
"module": "./dist/fountainjs.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/fountainjs.js",
|
|
14
|
+
"require": "./dist/fountainjs.cjs",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./react": {
|
|
18
|
+
"import": "./dist/fountainjs-react.js",
|
|
19
|
+
"require": "./dist/fountainjs-react.cjs",
|
|
20
|
+
"types": "./dist/react/index.d.ts"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/yourusername/fountainjs.git"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/yourusername/fountainjs#readme",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/yourusername/fountainjs/issues"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"editor",
|
|
39
|
+
"text-editor",
|
|
40
|
+
"rich-text",
|
|
41
|
+
"wysiwyg",
|
|
42
|
+
"react",
|
|
43
|
+
"modular",
|
|
44
|
+
"typescript"
|
|
45
|
+
],
|
|
46
|
+
"scripts": {
|
|
47
|
+
"dev": "vite",
|
|
48
|
+
"build": "vite build -c vite.lib.config.ts && tsc --declaration --emitDeclarationOnly --outDir dist",
|
|
49
|
+
"build:lib": "vite build -c vite.lib.config.ts",
|
|
50
|
+
"preview": "vite preview",
|
|
51
|
+
"lint": "tsc --noEmit",
|
|
52
|
+
"type-check": "tsc --noEmit"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
56
|
+
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependenciesMeta": {
|
|
59
|
+
"react": {
|
|
60
|
+
"optional": true
|
|
61
|
+
},
|
|
62
|
+
"react-dom": {
|
|
63
|
+
"optional": true
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@types/react": "^18.2.0",
|
|
68
|
+
"@types/react-dom": "^18.2.0",
|
|
69
|
+
"@vitejs/plugin-react": "^4.0.0",
|
|
70
|
+
"react": "^18.2.0",
|
|
71
|
+
"react-dom": "^18.2.0",
|
|
72
|
+
"typescript": "^5.0.0",
|
|
73
|
+
"vite": "^4.3.9",
|
|
74
|
+
"vite-tsconfig-paths": "^5.1.4"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { EditorState } from './state';
|
|
2
|
+
import { Schema, SchemaSpec } from './schema';
|
|
3
|
+
import { Transaction } from './transaction';
|
|
4
|
+
import { Plugin } from './plugin';
|
|
5
|
+
export interface EditorConfig { schema: SchemaSpec; state?: EditorState; plugins?: Plugin[]; }
|
|
6
|
+
type StateChangeCallback = (newState: EditorState) => void;
|
|
7
|
+
export class Editor {
|
|
8
|
+
private _state: EditorState; private subscribers: Set<StateChangeCallback> = new Set();
|
|
9
|
+
constructor(config: { state: EditorState }) { this._state = config.state; }
|
|
10
|
+
get state(): EditorState { return this._state; }
|
|
11
|
+
createTransaction(): Transaction { return this.state.createTransaction(); }
|
|
12
|
+
dispatch(transaction: Transaction): void { const newState = this._state.apply(transaction); if (newState === this._state) { return; } this._state = newState; this.subscribers.forEach(callback => callback(newState)); }
|
|
13
|
+
subscribe(callback: StateChangeCallback): () => void { this.subscribers.add(callback); return () => this.subscribers.delete(callback); }
|
|
14
|
+
}
|
|
15
|
+
export function createEditor(config: EditorConfig): Editor { const schema = new Schema(config.schema); const plugins = config.plugins || []; const state = config.state || EditorState.create({ schema, plugins, }); return new Editor({ state }); }
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { EditorState } from './state';
|
|
2
|
+
import { Transaction } from './transaction';
|
|
3
|
+
export interface PluginSpec { state?: { init: (config: any, state: EditorState) => any; apply: (tr: Transaction, value: any, oldState: EditorState) => any; }; }
|
|
4
|
+
export class Plugin { public readonly spec: PluginSpec; constructor(spec: PluginSpec) { this.spec = spec; } }
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MarkType, Schema } from './schema';
|
|
2
|
+
export class Mark {
|
|
3
|
+
public readonly type: MarkType;
|
|
4
|
+
public readonly attrs: { [name: string]: any };
|
|
5
|
+
constructor(type: MarkType, attrs: { [name: string]: any }) { this.type = type; this.attrs = attrs; }
|
|
6
|
+
static fromJSON(schema: Schema, json: any): Mark { const type = schema.marks[json.type]; if (!type) throw new Error(`Unknown mark type: ${json.type}`); return new Mark(type, { ...json.attrs }); }
|
|
7
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Node } from './node';
|
|
2
|
+
|
|
3
|
+
export type DOMOutputSpec = string | [string, any?, ...any[]];
|
|
4
|
+
export interface AttributeSpec { default?: any; }
|
|
5
|
+
export interface NodeSpec {
|
|
6
|
+
content?: string;
|
|
7
|
+
attrs?: { [name: string]: AttributeSpec };
|
|
8
|
+
group?: string;
|
|
9
|
+
inline?: boolean;
|
|
10
|
+
toDOM?: (node: Node) => DOMOutputSpec;
|
|
11
|
+
nodeView?: new (node: Node, view: any, getPos: () => number) => any;
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { NodeType } from './schema';
|
|
2
|
+
import type { Mark } from './mark';
|
|
3
|
+
export class Node {
|
|
4
|
+
public readonly type: NodeType;
|
|
5
|
+
public readonly attrs: { [name: string]: any };
|
|
6
|
+
public readonly content: Node[];
|
|
7
|
+
public readonly text?: string;
|
|
8
|
+
public readonly marks: readonly Mark[];
|
|
9
|
+
constructor(type: NodeType, attrs: { [name:string]: any }, content: Node[] = [], text?: string, marks: readonly Mark[] = [],) { this.type = type; this.attrs = attrs; this.content = content; this.text = text; this.marks = marks; }
|
|
10
|
+
get isText(): boolean { return this.type.name === 'text'; }
|
|
11
|
+
withText(text: string): Node { if (!this.isText) throw new Error('Cannot call withText on a non-text node.'); return new Node(this.type, this.attrs, [], text, this.marks); }
|
|
12
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { NodeSpec } from './node-spec';
|
|
2
|
+
import { MarkSpec } from './mark-spec';
|
|
3
|
+
export interface SchemaSpec { nodes: { [name: string]: NodeSpec }; marks?: { [name: string]: MarkSpec }; }
|
|
4
|
+
export class NodeType {
|
|
5
|
+
public readonly name: string; public readonly spec: NodeSpec; public readonly isBlock: boolean; public readonly isInline: boolean;
|
|
6
|
+
constructor(name: string, spec: NodeSpec) { this.name = name; this.spec = spec; this.isInline = !!spec.inline; this.isBlock = !this.isInline; }
|
|
7
|
+
}
|
|
8
|
+
export class MarkType {
|
|
9
|
+
public readonly name: string; public readonly spec: MarkSpec;
|
|
10
|
+
constructor(name: string, spec: MarkSpec) { this.name = name; this.spec = spec; }
|
|
11
|
+
}
|
|
12
|
+
export class Schema {
|
|
13
|
+
public readonly spec: SchemaSpec; public readonly nodes: { [name: string]: NodeType }; public readonly marks: { [name: string]: MarkType };
|
|
14
|
+
constructor(spec: SchemaSpec) { this.spec = spec; this.nodes = this.compileNodes(spec.nodes); this.marks = this.compileMarks(spec.marks || {}); }
|
|
15
|
+
private compileNodes(nodes: { [name: string]: NodeSpec }): { [name: string]: NodeType; } { const nodeTypes: { [name: string]: NodeType } = {}; for (const name in nodes) { nodeTypes[name] = new NodeType(name, nodes[name]); } return nodeTypes; }
|
|
16
|
+
private compileMarks(marks: { [name: string]: MarkSpec }): { [name: string]: MarkType; } { const markTypes: { [name: string]: MarkType } = {}; for (const name in marks) { markTypes[name] = new MarkType(name, marks[name]); } return markTypes; }
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class Selection {
|
|
2
|
+
// A selection is defined by a single path and a start/end offset within that path's node.
|
|
3
|
+
constructor(
|
|
4
|
+
public readonly path: number[],
|
|
5
|
+
public readonly from: number,
|
|
6
|
+
public readonly to: number,
|
|
7
|
+
) {}
|
|
8
|
+
|
|
9
|
+
// A collapsed selection (cursor)
|
|
10
|
+
static createCursor(path: number[], offset: number): Selection {
|
|
11
|
+
return new Selection(path, offset, offset);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get isCollapsed(): boolean {
|
|
15
|
+
return this.from === this.to;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Schema, Node } from './schema';
|
|
2
|
+
import { Transaction } from './transaction';
|
|
3
|
+
import { Selection } from './selection';
|
|
4
|
+
import { Plugin } from './plugin';
|
|
5
|
+
export interface EditorStateConfig { schema: Schema; doc?: Node; selection?: Selection; plugins?: Plugin[]; pluginStates?: any[]; }
|
|
6
|
+
export class EditorState {
|
|
7
|
+
public readonly schema: Schema; public readonly doc: Node; public readonly selection: Selection; public readonly plugins: Plugin[]; public readonly pluginStates: any[];
|
|
8
|
+
constructor(config: EditorStateConfig) { this.schema = config.schema; this.doc = config.doc || this.createDefaultDoc(config.schema); this.selection = config.selection || Selection.createCursor([0, 0], 19); this.plugins = config.plugins || []; if (config.pluginStates) { this.pluginStates = config.pluginStates; } else { this.pluginStates = this.plugins.map(p => p.spec.state?.init({}, this)); } }
|
|
9
|
+
static create(config: { schema: Schema; plugins?: Plugin[] }): EditorState { return new EditorState(config); }
|
|
10
|
+
apply(tr: Transaction): EditorState { const newPluginStates = this.plugins.map((plugin, i) => { const stateSpec = plugin.spec.state; return stateSpec ? stateSpec.apply(tr, this.pluginStates[i], this) : this.pluginStates[i]; }); const newDoc = tr.doc; const newSelection = tr.selectionSet ? tr.selection : this.selection; return new EditorState({ schema: this.schema, doc: newDoc, selection: newSelection, plugins: this.plugins, pluginStates: newPluginStates, }); }
|
|
11
|
+
createTransaction(): Transaction { return new Transaction(this.doc); }
|
|
12
|
+
private createDefaultDoc(schema: Schema): Node { const docType = schema.nodes.doc; const paraType = schema.nodes.paragraph; const textType = schema.nodes.text; if (!docType || !paraType || !textType) throw new Error('Schema is missing core nodes.'); const textNode = new Node(textType, {}, [], 'Start typing here...'); const paragraphNode = new Node(paraType, {}, [textNode]); return new Node(docType, {}, [paragraphNode]); }
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Step } from './step';
|
|
2
|
+
import { Node, Mark } from '../schema';
|
|
3
|
+
export class AddMarkStep extends Step {
|
|
4
|
+
constructor(public readonly path: number[], public readonly mark: Mark) { super(); }
|
|
5
|
+
apply(doc: Node): Node {
|
|
6
|
+
let node = doc; let parents: Node[] = [];
|
|
7
|
+
for (const index of this.path) { parents.push(node); node = node.content[index]; }
|
|
8
|
+
if (!node || !node.isText) return doc;
|
|
9
|
+
const newMarks = [this.mark, ...node.marks.filter(existing => existing.type !== this.mark.type)];
|
|
10
|
+
const newTextNode = new Node(node.type, node.attrs, [], node.text, newMarks);
|
|
11
|
+
let newDoc: Node = newTextNode;
|
|
12
|
+
for (let i = parents.length - 1; i >= 0; i--) {
|
|
13
|
+
const parent = parents[i]; const newContent = [...parent.content];
|
|
14
|
+
newContent[this.path[i]] = newDoc;
|
|
15
|
+
newDoc = new Node(parent.type, parent.attrs, newContent, parent.text, parent.marks);
|
|
16
|
+
}
|
|
17
|
+
return newDoc;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './step';
|
|
2
|
+
export * from './replace-step';
|
|
3
|
+
export * from './insert-text-step';
|
|
4
|
+
export * from './replace-text-step'
|
|
5
|
+
export * from './add-mark-step';
|
|
6
|
+
export * from './remove-mark-step';
|
|
7
|
+
export * from './set-node-attrs-step';
|
|
8
|
+
export * from './transform';
|
|
9
|
+
export * from './transaction';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Step } from './step';
|
|
2
|
+
import { Node } from '../schema';
|
|
3
|
+
export class InsertTextStep extends Step {
|
|
4
|
+
constructor( public readonly path: number[], public readonly offset: number, public readonly text: string, ) { super(); }
|
|
5
|
+
apply(doc: Node): Node {
|
|
6
|
+
let node = doc; let parents: Node[] = [];
|
|
7
|
+
for (const index of this.path) { parents.push(node); node = node.content[index]; }
|
|
8
|
+
if (!node || !node.isText) throw new Error('Target for InsertTextStep is not a text node.');
|
|
9
|
+
const newTextNode = node.withText((node.text || '').slice(0, this.offset) + this.text + (node.text || '').slice(this.offset));
|
|
10
|
+
let newDoc: Node = newTextNode;
|
|
11
|
+
for (let i = parents.length - 1; i >= 0; i--) {
|
|
12
|
+
const parent = parents[i]; const newContent = [...parent.content];
|
|
13
|
+
newContent[this.path[i]] = newDoc;
|
|
14
|
+
newDoc = new Node(parent.type, parent.attrs, newContent, parent.text, parent.marks);
|
|
15
|
+
}
|
|
16
|
+
return newDoc;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Step } from './step';
|
|
2
|
+
import { Node, MarkType } from '../schema';
|
|
3
|
+
export class RemoveMarkStep extends Step {
|
|
4
|
+
constructor(public readonly from: number, public readonly to: number, public readonly markType: MarkType) { super(); }
|
|
5
|
+
apply(doc: Node): Node {
|
|
6
|
+
const newContent = doc.content.map((node, i) => {
|
|
7
|
+
if (i >= this.from && i < this.to) {
|
|
8
|
+
if (node.type.name === 'paragraph') {
|
|
9
|
+
const newParaContent = node.content.map(child => { if (child.isText) { const newMarks = child.marks.filter(m => m.type !== this.markType); return new Node(child.type, child.attrs, [], child.text, newMarks); } return child; });
|
|
10
|
+
return new Node(node.type, node.attrs, newParaContent);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return node;
|
|
14
|
+
});
|
|
15
|
+
return new Node(doc.type, doc.attrs, newContent);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Node } from '../schema/node';
|
|
2
|
+
import { Step } from './step';
|
|
3
|
+
export class ReplaceStep extends Step {
|
|
4
|
+
constructor( public readonly from: number, public readonly to: number, public readonly content: Node[], ) { super(); }
|
|
5
|
+
apply(doc: Node): Node { if (this.from > this.to || this.from > doc.content.length || this.to > doc.content.length) { throw new Error('ReplaceStep apply error: Invalid range'); } const newContent = [ ...doc.content.slice(0, this.from), ...this.content, ...doc.content.slice(this.to), ]; return new Node(doc.type, doc.attrs, newContent); }
|
|
6
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Step } from './step';
|
|
2
|
+
import { Node } from '../schema';
|
|
3
|
+
|
|
4
|
+
export class ReplaceTextStep extends Step {
|
|
5
|
+
constructor(
|
|
6
|
+
public readonly path: number[],
|
|
7
|
+
public readonly from: number,
|
|
8
|
+
public readonly to: number,
|
|
9
|
+
public readonly text: string,
|
|
10
|
+
) {
|
|
11
|
+
super();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
apply(doc: Node): Node {
|
|
15
|
+
let node = doc; let parents: Node[] = [];
|
|
16
|
+
for (const index of this.path) { parents.push(node); node = node.content[index]; }
|
|
17
|
+
|
|
18
|
+
if (!node || !node.isText) throw new Error('Target for ReplaceTextStep is not a text node.');
|
|
19
|
+
|
|
20
|
+
const oldText = node.text || '';
|
|
21
|
+
const newTextContent = oldText.slice(0, this.from) + this.text + oldText.slice(this.to);
|
|
22
|
+
const newTextNode = node.withText(newTextContent);
|
|
23
|
+
|
|
24
|
+
let newDoc: Node = newTextNode;
|
|
25
|
+
for (let i = parents.length - 1; i >= 0; i--) {
|
|
26
|
+
const parent = parents[i];
|
|
27
|
+
const newContent = [...parent.content];
|
|
28
|
+
newContent[this.path[i]] = newDoc;
|
|
29
|
+
newDoc = new Node(parent.type, parent.attrs, newContent, parent.text, parent.marks);
|
|
30
|
+
}
|
|
31
|
+
return newDoc;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Step } from './step';
|
|
2
|
+
import { Node } from '../schema';
|
|
3
|
+
|
|
4
|
+
export class SetNodeAttrsStep extends Step {
|
|
5
|
+
constructor(
|
|
6
|
+
public readonly path: number[],
|
|
7
|
+
public readonly attrs: { [key: string]: any },
|
|
8
|
+
) {
|
|
9
|
+
super();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
apply(doc: Node): Node {
|
|
13
|
+
let node = doc; let parents: Node[] = [];
|
|
14
|
+
for (const index of this.path) { parents.push(node); node = node.content[index]; }
|
|
15
|
+
|
|
16
|
+
if (!node) throw new Error('No node found at path');
|
|
17
|
+
|
|
18
|
+
const newAttrs = { ...node.attrs, ...this.attrs };
|
|
19
|
+
const updatedNode = new Node(node.type, newAttrs, node.content, node.text, node.marks);
|
|
20
|
+
|
|
21
|
+
let newDoc: Node = updatedNode;
|
|
22
|
+
for (let i = parents.length - 1; i >= 0; i--) {
|
|
23
|
+
const parent = parents[i];
|
|
24
|
+
const newContent = [...parent.content];
|
|
25
|
+
newContent[this.path[i]] = newDoc;
|
|
26
|
+
newDoc = new Node(parent.type, parent.attrs, newContent, parent.text, parent.marks);
|
|
27
|
+
}
|
|
28
|
+
return newDoc;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Transform } from './transform';
|
|
2
|
+
import { Node } from '../schema/node';
|
|
3
|
+
import { Selection } from '../selection';
|
|
4
|
+
export class Transaction extends Transform {
|
|
5
|
+
public selection: Selection; public selectionSet = false;
|
|
6
|
+
constructor(doc: Node) { super(doc); this.selection = Selection.createCursor([], 0); }
|
|
7
|
+
setSelection(selection: Selection): this { this.selection = selection; this.selectionSet = true; return this; }
|
|
8
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Node, Mark, MarkType } from '../schema';
|
|
2
|
+
import { Step } from './step';
|
|
3
|
+
import { ReplaceStep } from './replace-step';
|
|
4
|
+
import { ReplaceTextStep } from './replace-text-step'; // <-- Import new step
|
|
5
|
+
import { AddMarkStep } from './add-mark-step';
|
|
6
|
+
import { RemoveMarkStep } from './remove-mark-step';
|
|
7
|
+
import { SetNodeAttrsStep } from './set-node-attrs-step';
|
|
8
|
+
|
|
9
|
+
export class Transform {
|
|
10
|
+
public readonly originalDoc: Node; public doc: Node; public steps: Step[];
|
|
11
|
+
constructor(doc: Node) { this.originalDoc = doc; this.doc = doc; this.steps = []; }
|
|
12
|
+
step(step: Step): this { const newDoc = step.apply(this.doc); if (newDoc) { this.doc = newDoc; this.steps.push(step); } return this; }
|
|
13
|
+
replace(from: number, to: number, content: Node[] = []): this { return this.step(new ReplaceStep(from, to, content)); }
|
|
14
|
+
|
|
15
|
+
// --- REPLACE `insertText` WITH THIS NEW METHOD ---
|
|
16
|
+
replaceText(path: number[], from: number, to: number, text: string): this {
|
|
17
|
+
return this.step(new ReplaceTextStep(path, from, to, text));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
addMark(path: number[], mark: Mark): this { return this.step(new AddMarkStep(path, mark)); }
|
|
21
|
+
removeMark(from: number, to: number, markType: MarkType): this { return this.step(new RemoveMarkStep(from, to, markType)); }
|
|
22
|
+
setNodeAttrs(path: number[], attrs: { [key: string]: any }): this { return this.step(new SetNodeAttrsStep(path, attrs)); }
|
|
23
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// All Node imports
|
|
2
|
+
import { doc } from './nodes/doc';
|
|
3
|
+
import { paragraph } from './nodes/paragraph';
|
|
4
|
+
import { text } from './nodes/text';
|
|
5
|
+
import { heading } from './nodes/heading';
|
|
6
|
+
import { imageSuper } from './nodes/image-super';
|
|
7
|
+
import { figcaption } from './nodes/figcaption';
|
|
8
|
+
import { table } from './nodes/table';
|
|
9
|
+
import { tableRow } from './nodes/table-row';
|
|
10
|
+
import { tableCell } from './nodes/table-cell';
|
|
11
|
+
import { bulletList } from './nodes/bullet-list';
|
|
12
|
+
import { listItem } from './nodes/list-item';
|
|
13
|
+
|
|
14
|
+
// All Mark imports
|
|
15
|
+
import { strong } from './marks/strong';
|
|
16
|
+
import { em } from './marks/em';
|
|
17
|
+
|
|
18
|
+
// All Plugin imports
|
|
19
|
+
import { historyPlugin } from './plugins/history';
|
|
20
|
+
import { markdownShortcutsPlugin } from './plugins/markdown-shortcuts';
|
|
21
|
+
|
|
22
|
+
// Core imports for defining the schema
|
|
23
|
+
import { SchemaSpec } from '../core';
|
|
24
|
+
|
|
25
|
+
// --- Export individual items for granular use ---
|
|
26
|
+
export { doc } from './nodes/doc';
|
|
27
|
+
export { paragraph } from './nodes/paragraph';
|
|
28
|
+
export { text } from './nodes/text';
|
|
29
|
+
export { heading } from './nodes/heading';
|
|
30
|
+
export { imageSuper } from './nodes/image-super';
|
|
31
|
+
export { figcaption } from './nodes/figcaption';
|
|
32
|
+
export { table } from './nodes/table';
|
|
33
|
+
export { tableRow } from './nodes/table-row';
|
|
34
|
+
export { tableCell } from './nodes/table-cell';
|
|
35
|
+
export { bulletList } from './nodes/bullet-list';
|
|
36
|
+
export { listItem } from './nodes/list-item';
|
|
37
|
+
|
|
38
|
+
export { strong } from './marks/strong';
|
|
39
|
+
export { em } from './marks/em';
|
|
40
|
+
|
|
41
|
+
export { historyPlugin, undo, redo } from './plugins/history';
|
|
42
|
+
export { markdownShortcutsPlugin } from './plugins/markdown-shortcuts';
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
// --- Define and Export the Core Schema Spec (ONCE) ---
|
|
46
|
+
export const CoreSchemaSpec: SchemaSpec = {
|
|
47
|
+
nodes: {
|
|
48
|
+
doc,
|
|
49
|
+
paragraph,
|
|
50
|
+
text,
|
|
51
|
+
heading,
|
|
52
|
+
image_super: imageSuper,
|
|
53
|
+
figcaption,
|
|
54
|
+
table,
|
|
55
|
+
table_row: tableRow,
|
|
56
|
+
table_cell: tableCell,
|
|
57
|
+
bullet_list: bulletList,
|
|
58
|
+
list_item: listItem,
|
|
59
|
+
},
|
|
60
|
+
marks: {
|
|
61
|
+
strong,
|
|
62
|
+
em,
|
|
63
|
+
},
|
|
64
|
+
};
|