@firecms/editor 3.0.0-beta.7 → 3.0.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.umd.js CHANGED
@@ -1,6 +1,1606 @@
1
- (function(u,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("react/jsx-runtime"),require("react"),require("@tiptap/extension-underline"),require("@tiptap/extension-text-style"),require("@tiptap/extension-color"),require("tiptap-markdown"),require("@tiptap/extension-highlight"),require("@tiptap/react"),require("jotai"),require("@radix-ui/react-slot"),require("tunnel-rat"),require("cmdk"),require("@tiptap/starter-kit"),require("@tiptap/extension-horizontal-rule"),require("@tiptap/extension-link"),require("@tiptap/extension-image"),require("@tiptap/extension-placeholder"),require("@tiptap/extension-task-item"),require("@tiptap/extension-task-list"),require("@tiptap/core"),require("@tiptap/suggestion"),require("tippy.js"),require("@firecms/ui"),require("prosemirror-state"),require("@tiptap/pm/view"),require("@tiptap/pm/state")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","@tiptap/extension-underline","@tiptap/extension-text-style","@tiptap/extension-color","tiptap-markdown","@tiptap/extension-highlight","@tiptap/react","jotai","@radix-ui/react-slot","tunnel-rat","cmdk","@tiptap/starter-kit","@tiptap/extension-horizontal-rule","@tiptap/extension-link","@tiptap/extension-image","@tiptap/extension-placeholder","@tiptap/extension-task-item","@tiptap/extension-task-list","@tiptap/core","@tiptap/suggestion","tippy.js","@firecms/ui","prosemirror-state","@tiptap/pm/view","@tiptap/pm/state"],n):(u=typeof globalThis<"u"?globalThis:u||self,n(u["FireCMS Editor"]={},u.jsxRuntime,u.React,u.TiptapUnderline,u.TextStyle,u.extensionColor,u.tiptapMarkdown,u.Highlight,u.react,u.jotai,u.reactSlot,u.tunnel,u.cmdk,u.StarterKit,u.HorizontalRule,u.TiptapLink,u.TiptapImage,u.Placeholder,u.extensionTaskItem,u.extensionTaskList,u.core,u.Suggestion,u.tippy,u.ui,u.prosemirrorState,u.view,u.state))})(this,function(u,n,g,W,Q,Z,_,V,b,y,X,Y,C,J,G,$,j,R,ee,te,w,re,oe,s,ne,A,E){"use strict";const T=y.createStore(),se=({children:e})=>n.jsx(y.Provider,{store:T,children:e}),ae=g.forwardRef(({children:e,tippyOptions:r,...t},o)=>{const{editor:i}=b.useCurrentEditor(),l=g.useRef(null);g.useEffect(()=>{!l.current||!r?.placement||(l.current.setProps({placement:r.placement}),l.current.popperInstance?.update())},[r?.placement]);const a=g.useMemo(()=>({shouldShow:({editor:m,state:p})=>{const{selection:h}=p,{empty:k}=h;return!(m.isActive("image")||k||b.isNodeSelection(h))},tippyOptions:{onCreate:m=>{l.current=m},moveTransition:"transform 0.15s ease-out",...r},...t}),[t,r]);return i?n.jsx("div",{ref:o,children:n.jsx(b.BubbleMenu,{editor:i,...a,children:e})}):null}),S=g.forwardRef(({children:e,asChild:r,onSelect:t,...o},i)=>{const{editor:l}=b.useCurrentEditor(),a=r?X.Slot:"div";return l?n.jsx(a,{ref:i,...o,onClick:()=>t?.(l),children:e}):null});S.displayName="EditorBubbleItem";const q=Y(),D=y.atom(""),z=y.atom(null),ie=({query:e,range:r})=>{const t=y.useSetAtom(D,{store:T}),o=y.useSetAtom(z,{store:T});return g.useEffect(()=>{t(e)},[e,t]),g.useEffect(()=>{o(r)},[r,o]),g.useEffect(()=>{const i=["ArrowUp","ArrowDown","Enter"],l=a=>{if(i.includes(a.key)){a.preventDefault();const c=document.querySelector("#slash-command");c&&c.dispatchEvent(new KeyboardEvent("keydown",{key:a.key,cancelable:!0,bubbles:!0}))}};return document.addEventListener("keydown",l),()=>{document.removeEventListener("keydown",l)}},[]),n.jsx(q.Out,{})},ce=g.forwardRef(({children:e,className:r,...t},o)=>{const i=g.useRef(null),[l,a]=y.useAtom(D);return n.jsx(q.In,{children:n.jsxs(C.Command,{ref:o,onKeyDown:c=>{c.stopPropagation()},id:"slash-command",className:r,...t,children:[n.jsx(C.Command.Input,{value:l,onValueChange:a,style:{display:"none"}}),n.jsx(C.Command.List,{ref:i,children:e})]})})}),F=g.forwardRef(({children:e,onCommand:r,...t},o)=>{const{editor:i}=b.useCurrentEditor(),l=y.useAtomValue(z);return!i||!l?null:n.jsx(C.CommandItem,{ref:o,...t,onSelect:()=>r({editor:i,range:l}),children:e})});F.displayName="EditorCommandItem";const le=C.CommandEmpty,de=w.Extension.create({name:"slash-command",addOptions(){return{suggestion:{char:"/",command:({editor:e,range:r,props:t})=>{t.command({editor:e,range:r})}}}},addProseMirrorPlugins(){return[re({editor:this.editor,...this.options.suggestion})]}}),ue=()=>{let e=null,r=null;return{onStart:t=>{e=new b.ReactRenderer(ie,{props:t,editor:t.editor}),r=oe("body",{getReferenceClientRect:t.clientRect,appendTo:()=>document.body,content:e.element,showOnCreate:!0,interactive:!0,trigger:"manual",placement:"bottom-start"})},onUpdate:t=>{e?.updateProps(t),r&&r[0].setProps({getReferenceClientRect:t.clientRect})},onKeyDown:t=>t.event.key==="Escape"?(r?.[0].hide(),!0):e?.ref?.onKeyDown(t),onExit:()=>{r?.[0].destroy(),e?.destroy()}}},me=e=>e,pe=R.configure({placeholder:({node:e})=>e.type.name==="heading"?`Heading ${e.attrs.level}`:"Press '/' for commands",includeChildren:!0}),ge=G.extend({addInputRules(){return[new w.InputRule({find:/^(?:---|—-|___\s|\*\*\*\s)$/,handler:({state:e,range:r})=>{const t={},{tr:o}=e,i=r.from,l=r.to;o.insert(i-1,this.type.create(t)).delete(o.mapping.map(i),o.mapping.map(l))}})]}}),U=[{name:"Text",icon:s.TextFieldsIcon,command:e=>e?.chain().focus().toggleNode("paragraph","paragraph").run(),isActive:e=>(e?.isActive("paragraph")&&!e?.isActive("bulletList")&&!e?.isActive("orderedList"))??!1},{name:"Heading 1",icon:s.LooksOneIcon,command:e=>e?.chain().focus().toggleHeading({level:1}).run(),isActive:e=>e?.isActive("heading",{level:1})??!1},{name:"Heading 2",icon:s.LooksTwoIcon,command:e=>e?.chain().focus().toggleHeading({level:2}).run(),isActive:e=>e?.isActive("heading",{level:2})??!1},{name:"Heading 3",icon:s.Looks3Icon,command:e=>e?.chain().focus().toggleHeading({level:3}).run(),isActive:e=>e?.isActive("heading",{level:3})??!1},{name:"To-do List",icon:s.CheckBoxIcon,command:e=>e?.chain().focus().toggleTaskList().run(),isActive:e=>e?.isActive("taskItem")??!1},{name:"Bullet List",icon:s.FormatListBulletedIcon,command:e=>e?.chain().focus().toggleBulletList().run(),isActive:e=>e?.isActive("bulletList")??!1},{name:"Numbered List",icon:s.FormatListNumberedIcon,command:e=>e?.chain().focus().toggleOrderedList().run(),isActive:e=>e?.isActive("orderedList")??!1},{name:"Quote",icon:s.FormatQuoteIcon,command:e=>e?.chain().focus().toggleNode("paragraph","paragraph").toggleBlockquote().run(),isActive:e=>e?.isActive("blockquote")??!1},{name:"Code",icon:s.CodeIcon,command:e=>e?.chain().focus().toggleCodeBlock().run(),isActive:e=>e?.isActive("codeBlock")??!1}],fe=({open:e,onOpenChange:r})=>{const{editor:t}=b.useCurrentEditor();if(!t)return null;const o=U.filter(i=>i.isActive(t)).pop()??{name:"Multiple"};return n.jsx(s.Popover,{sideOffset:5,align:"start",className:"w-48 p-1",trigger:n.jsxs(s.Button,{variant:"text",className:"gap-2 rounded-none",color:"text",children:[n.jsx("span",{className:"whitespace-nowrap text-sm",children:o.name}),n.jsx(s.ExpandMoreIcon,{size:"small"})]}),modal:!0,open:e,onOpenChange:r,children:U.map((i,l)=>n.jsxs(S,{onSelect:a=>{i.command(a),r(!1)},className:"flex cursor-pointer items-center justify-between rounded px-2 py-1 text-sm hover:bg-blue-50 hover:dark:bg-gray-700 text-gray-900 dark:text-white",children:[n.jsxs("div",{className:"flex items-center space-x-2",children:[n.jsx(i.icon,{size:"smallest"}),n.jsx("span",{children:i.name})]}),o.name===i.name&&n.jsx(s.CheckIcon,{size:"smallest"})]},l))})};function he(e){try{return new URL(e),!0}catch{return!1}}function xe(e){if(he(e))return e;try{return e.includes(".")&&!e.includes(" ")?new URL(`https://${e}`).toString():null}catch{return null}}const ke=({open:e,onOpenChange:r})=>{const t=g.useRef(null),{editor:o}=b.useCurrentEditor();return g.useEffect(()=>{t.current&&t.current?.focus()}),o?n.jsx(s.Popover,{modal:!0,open:e,onOpenChange:r,trigger:n.jsx(s.Button,{variant:"text",className:"gap-2 rounded-none",color:"text",children:n.jsx("p",{className:s.cls("underline decoration-stone-400 underline-offset-4",{"text-blue-500":o.isActive("link")}),children:"Link"})}),children:n.jsxs("form",{onSubmit:i=>{const l=i.currentTarget;i.preventDefault();const a=l[0],c=xe(a.value);c&&o.chain().focus().setLink({href:c}).run()},className:"flex p-1",children:[n.jsx("input",{ref:t,autoFocus:e,placeholder:"Paste a link",defaultValue:o.getAttributes("link").href||"",className:"text-gray-900 dark:text-white flex-grow bg-transparent p-1 text-sm outline-none"}),o.getAttributes("link").href?n.jsx(s.Button,{size:"small",variant:"text",type:"button",color:"text",className:"flex items-center",onClick:()=>{o.chain().focus().unsetLink().run()},children:n.jsx(s.DeleteIcon,{size:"small"})}):n.jsx(s.Button,{size:"small",variant:"text",children:n.jsx(s.CheckIcon,{size:"small"})})]})}):null},be=()=>{const{editor:e}=b.useCurrentEditor();if(!e)return null;const r=[{name:"bold",isActive:t=>t?.isActive("bold")??!1,command:t=>t?.chain().focus().toggleBold().run(),icon:s.FormatBoldIcon},{name:"italic",isActive:t=>t?.isActive("italic")??!1,command:t=>t?.chain().focus().toggleItalic().run(),icon:s.FormatItalicIcon},{name:"underline",isActive:t=>t?.isActive("underline")??!1,command:t=>t?.chain().focus().toggleUnderline().run(),icon:s.FormatUnderlinedIcon},{name:"strike",isActive:t=>t?.isActive("strike")??!1,command:t=>t?.chain().focus().toggleStrike().run(),icon:s.FormatStrikethroughIcon},{name:"code",isActive:t=>t?.isActive("code")??!1,command:t=>t?.chain().focus().toggleCode().run(),icon:s.CodeIcon}];return n.jsx("div",{className:"flex",children:r.map((t,o)=>n.jsx(S,{onSelect:i=>{t.command(i)},children:n.jsx(s.Button,{size:"small",color:"text",className:"gap-2 rounded-none h-full",variant:"text",children:n.jsx(t.icon,{className:s.cls({"text-inherit":!t.isActive(e),"text-blue-500":t.isActive(e)})})})},o))})};function M(e,r,t,o=300){const i=g.useRef(!1),l=()=>{r(),i.current=!1},a=g.useRef(void 0);g.useEffect(()=>(i.current=!0,clearTimeout(a.current),a.current=setTimeout(l,o),()=>{t&&l()}),[t,e])}function I(e){return Array.isArray(e)?e.map(r=>I(r)):(typeof e=="object"&&e!==null&&(e.attrs&&typeof e.attrs=="object"&&"class"in e.attrs&&delete e.attrs.class,Object.keys(e).forEach(r=>{e[r]=I(e[r])})),e)}const ye=pe,ve=$.configure({HTMLAttributes:{class:s.cls("text-gray-600 dark:text-slate-300 underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer")}}),Ce=te.TaskList.configure({HTMLAttributes:{class:s.cls("not-prose")}}),we=ee.TaskItem.configure({HTMLAttributes:{class:s.cls("flex items-start my-4")},nested:!0}),Le=ge.configure({HTMLAttributes:{class:s.cls("mt-4 mb-6 border-t",s.defaultBorderMixin)}}),Ae=J.configure({bulletList:{HTMLAttributes:{class:s.cls("list-disc list-outside leading-3 -mt-2")}},orderedList:{HTMLAttributes:{class:s.cls("list-decimal list-outside leading-3 -mt-2")}},listItem:{HTMLAttributes:{class:s.cls("leading-normal -mb-2")}},blockquote:{HTMLAttributes:{class:s.cls("border-l-4 border-primary")}},codeBlock:{HTMLAttributes:{class:s.cls("rounded bg-blue-50 dark:bg-gray-700 border p-5 font-mono font-medium",s.defaultBorderMixin)}},code:{HTMLAttributes:{class:s.cls("rounded-md bg-slate-50 dark:bg-gray-700 px-1.5 py-1 font-mono font-medium"),spellcheck:"false"}},horizontalRule:!1,dropcursor:{color:"#DBEAFE",width:4},gapcursor:!1});async function K(e,r,t,o,i,l){const{schema:a}=r.state;let c=e.getState(r.state);const m=document.createElement("div"),p=document.createElement("img");p.setAttribute("class","opacity-40 rounded-lg border border-stone-200"),p.src=t.target?.result,m.appendChild(p);const h=A.Decoration.widget(o,m);c=c?.add(r.state.doc,[h]),r.dispatch(r.state.tr.setMeta(e,{decorationSet:c}));const k=await i(l);console.log("uploaded image",k);const x=a.nodes.image.create({src:k}),L=r.state.tr.replaceWith(o,o,x);c=c?.remove([h]),L.setMeta(e,{decorationSet:c}),r.dispatch(L)}const Ee=e=>{const r=new ne.Plugin({state:{init:()=>A.DecorationSet.empty,apply:(t,o)=>{const i=t.getMeta(r);return i&&i.decorationSet?i.decorationSet:o.map(t.mapping,t.doc)}},props:{handleDOMEvents:{drop:(t,o)=>{if(console.log("drop event",o),!o.dataTransfer?.files||o.dataTransfer?.files.length===0)return!1;o.preventDefault();const l=Array.from(o.dataTransfer.files).filter(a=>/image/i.test(a.type));return l.length===0?!1:(l.forEach(a=>{const c=t.posAtCoords({left:o.clientX,top:o.clientY});if(!c)return;const m=new FileReader;m.onload=async p=>{await K(r,t,p,c.pos,e,a)},m.readAsDataURL(a)}),!0)}},handlePaste(t,o,i){const l=Array.from(o.clipboardData?.items||[]),a=t.state.selection.from;console.log("pos",a);let c=!1;return l.forEach(m=>{const p=m.getAsFile();if(console.log("image",p),p){c=!0;const h=new FileReader;h.onload=async k=>{await K(r,t,k,a,e,p)},h.readAsDataURL(p)}}),c},decorations(t){return r.getState(t)}},view(t){return{update(o,i){const l=r.getState(i),a=r.getState(o.state);l!==a&&o.updateState(o.state)}}}});return r},Te=e=>j.extend({addProseMirrorPlugins(){return[Ee(e)]}}).configure({allowBase64:!0,HTMLAttributes:{class:s.cls("rounded-lg border",s.defaultBorderMixin)}}),Se=w.Extension.create({name:"CustomKeymap",addCommands(){return{selectTextWithinNodeBoundaries:()=>({editor:e,commands:r})=>{const{state:t}=e,{tr:o}=t,i=o.selection.$from.start(),l=o.selection.$to.end();return r.setTextSelection({from:i,to:l})}}},addKeyboardShortcuts(){return{"Mod-a":({editor:e})=>{const{state:r}=e,{tr:t}=r,o=t.selection.from,i=t.selection.to,l=t.selection.$from.start(),a=t.selection.$to.end();return o>l||i<a?(e.chain().selectTextWithinNodeBoundaries().run(),!0):!1}}}});function Me(e){const r=e.getBoundingClientRect();return{top:r.top,left:r.left,width:r.width}}function N(e){return document.elementsFromPoint(e.x,e.y).find(r=>r.parentElement?.matches?.(".ProseMirror")||r.matches(["li","p:not(:first-child)","pre","blockquote","h1, h2, h3, h4, h5, h6"].join(", ")))}function O(e,r,t){const o=e.getBoundingClientRect();return r.posAtCoords({left:o.left+50+t.dragHandleWidth,top:o.top+1})?.inside}function Ie(e){function r(a,c){if(c.focus(),!a.dataTransfer)return;const m=N({x:a.clientX+50+e.dragHandleWidth,y:a.clientY});if(!(m instanceof Element))return;const p=O(m,c,e);if(p==null||p<0)return;c.dispatch(c.state.tr.setSelection(E.NodeSelection.create(c.state.doc,p)));const h=c.state.selection.content(),{dom:k,text:x}=A.__serializeForClipboard(c,h);a.dataTransfer.clearData(),a.dataTransfer.setData("text/html",k.innerHTML),a.dataTransfer.setData("text/plain",x),a.dataTransfer.effectAllowed="copyMove",a.dataTransfer.setDragImage(m,0,0),c.dragging={slice:h,move:a.ctrlKey}}function t(a,c){c.focus(),c.dom.classList.remove("dragging");const m=N({x:a.clientX+50+e.dragHandleWidth,y:a.clientY});if(!(m instanceof Element))return;const p=O(m,c,e);p&&c.dispatch(c.state.tr.setSelection(E.NodeSelection.create(c.state.doc,p)))}let o=null;function i(){o&&o.classList.add("hide")}function l(){o&&o.classList.remove("hide")}return new E.Plugin({view:a=>(o=document.createElement("div"),o.draggable=!0,o.dataset.dragHandle="",o.classList.add("drag-handle"),o.addEventListener("dragstart",c=>{r(c,a)}),o.addEventListener("click",c=>{t(c,a)}),i(),a?.dom?.parentElement?.appendChild(o),{destroy:()=>{o?.remove?.(),o=null}}),props:{handleDOMEvents:{mousemove:(a,c)=>{if(!a.editable)return;const m=N({x:c.clientX+50+e.dragHandleWidth,y:c.clientY});if(!(m instanceof Element)){i();return}const p=window.getComputedStyle(m),h=parseInt(p.lineHeight,10),k=parseInt(p.paddingTop,10),x=Me(m);x.top+=(h-24)/2,x.top+=k,m.matches("ul:not([data-type=taskList]) li, ol li")&&(x.left-=e.dragHandleWidth),x.width=e.dragHandleWidth,o&&(o.style.left=`${x.left-x.width}px`,o.style.top=`${x.top}px`,l())},keydown:()=>{i()},mousewheel:()=>{i()},dragstart:a=>{a.dom.classList.add("dragging")},drop:a=>{a.dom.classList.remove("dragging")},dragend:a=>{a.dom.classList.remove("dragging")}}}})}const Ne=w.Extension.create({name:"dragAndDrop",addProseMirrorPlugins(){return[Ie({dragHandleWidth:24})]}}),Pe=({handleImageUpload:e,initialContent:r,onJsonContentChange:t,onHtmlContentChange:o,onMarkdownContentChange:i})=>{const l={handleDOMEvents:{keydown:(d,f)=>!!(["ArrowUp","ArrowDown","Enter"].includes(f.key)&&document.querySelector("#slash-command"))}},a=me([{title:"Text",description:"Just start typing with plain text.",searchTerms:["p","paragraph"],icon:n.jsx(s.TextFieldsIcon,{size:18}),command:({editor:d,range:f})=>{d.chain().focus().deleteRange(f).toggleNode("paragraph","paragraph").run()}},{title:"To-do List",description:"Track tasks with a to-do list.",searchTerms:["todo","task","list","check","checkbox"],icon:n.jsx(s.CheckBoxIcon,{size:18}),command:({editor:d,range:f})=>{d.chain().focus().deleteRange(f).toggleTaskList().run()}},{title:"Heading 1",description:"Big section heading.",searchTerms:["title","big","large"],icon:n.jsx(s.LooksOneIcon,{size:18}),command:({editor:d,range:f})=>{d.chain().focus().deleteRange(f).setNode("heading",{level:1}).run()}},{title:"Heading 2",description:"Medium section heading.",searchTerms:["subtitle","medium"],icon:n.jsx(s.LooksTwoIcon,{size:18}),command:({editor:d,range:f})=>{d.chain().focus().deleteRange(f).setNode("heading",{level:2}).run()}},{title:"Heading 3",description:"Small section heading.",searchTerms:["subtitle","small"],icon:n.jsx(s.Looks3Icon,{size:18}),command:({editor:d,range:f})=>{d.chain().focus().deleteRange(f).setNode("heading",{level:3}).run()}},{title:"Bullet List",description:"Create a simple bullet list.",searchTerms:["unordered","point"],icon:n.jsx(s.FormatListBulletedIcon,{size:18}),command:({editor:d,range:f})=>{d.chain().focus().deleteRange(f).toggleBulletList().run()}},{title:"Numbered List",description:"Create a list with numbering.",searchTerms:["ordered"],icon:n.jsx(s.FormatListNumberedIcon,{size:18}),command:({editor:d,range:f})=>{d.chain().focus().deleteRange(f).toggleOrderedList().run()}},{title:"Quote",description:"Capture a quote.",searchTerms:["blockquote"],icon:n.jsx(s.FormatQuoteIcon,{size:18}),command:({editor:d,range:f})=>d.chain().focus().deleteRange(f).toggleNode("paragraph","paragraph").toggleBlockquote().run()},{title:"Code",description:"Capture a code snippet.",searchTerms:["codeblock"],icon:n.jsx(s.CodeIcon,{size:18}),command:({editor:d,range:f})=>d.chain().focus().deleteRange(f).toggleCodeBlock().run()},{title:"Image",description:"Upload an image from your computer.",searchTerms:["photo","picture","media"],icon:n.jsx(s.ImageIcon,{size:18}),command:({editor:d,range:f})=>{d.chain().focus().deleteRange(f).run();const v=document.createElement("input");v.type="file",v.accept="image/*",v.onchange=async()=>{if(v.files?.length){if(!v.files[0])return;d.view.state.selection.from}},v.click()}}]),c=de.configure({suggestion:{items:()=>a,render:ue}}),m=g.useMemo(()=>Te(e),[]),p=[W,Q,Z.Color,V.configure({multicolor:!0}),_.Markdown.configure({html:!1,transformCopiedText:!0}),Se,Ne,Ae,ye,ve,m,Ce,we,Le,c],[h,k]=g.useState(!1),[x,L]=g.useState(!1);s.useInjectStyles("Editor",He);const P=g.useRef(null),[qe,De]=g.useState(null),[B,ze]=g.useState(null),[H,Fe]=g.useState(null),Ue=d=>{P.current=d,i&&De(d.storage.markdown.getMarkdown()),t&&ze(I(d.getJSON())),o&&Fe(d.getHTML())};return M(qe,()=>{if(P.current){const d=P.current.storage.markdown.getMarkdown();i?.(Be(d))}},!1,500),M(B,()=>{B&&t?.(B)},!1,500),M(H,()=>{H&&o?.(H)},!1,500),r?n.jsx("div",{className:"relative w-full p-8",children:n.jsx(se,{children:n.jsx("div",{className:"relative min-h-[500px] w-full bg-white dark:bg-gray-950 rounded-lg",children:n.jsxs(b.EditorProvider,{content:r,extensions:p,editorProps:{...l,attributes:{class:"prose-lg prose-headings:font-title font-default focus:outline-none max-w-full p-12"}},onUpdate:({editor:d})=>{console.debug("Editor updated"),Ue(d)},children:[n.jsxs(ce,{className:s.cls("text-gray-900 dark:text-white z-50 h-auto max-h-[330px] w-72 overflow-y-auto rounded-md border bg-white dark:bg-gray-900 px-1 py-2 shadow transition-all",s.defaultBorderMixin),children:[n.jsx(le,{className:"px-2 text-gray-700 dark:text-slate-300",children:"No results"}),a.map(d=>n.jsxs(F,{value:d.title,onCommand:f=>d?.command?.(f),className:"flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm hover:bg-blue-50 hover:dark:bg-gray-700 aria-selected:bg-blue-50 aria-selected:dark:bg-gray-700",children:[n.jsx("div",{className:s.cls("flex h-10 w-10 items-center justify-center rounded-md border bg-white dark:bg-gray-900",s.defaultBorderMixin),children:d.icon}),n.jsxs("div",{children:[n.jsx("p",{className:"font-medium",children:d.title}),n.jsx("p",{className:"text-xs text-gray-700 dark:text-slate-300",children:d.description})]})]},d.title))]}),n.jsxs(ae,{tippyOptions:{placement:"top"},className:s.cls("flex w-fit max-w-[90vw] h-10 overflow-hidden rounded border bg-white dark:bg-gray-900 shadow",s.defaultBorderMixin),children:[n.jsx(fe,{open:h,onOpenChange:k}),n.jsx(s.Separator,{orientation:"vertical"}),n.jsx(ke,{open:x,onOpenChange:L}),n.jsx(s.Separator,{orientation:"vertical"}),n.jsx(be,{})]})]})})})}):null};function Be(e){const r=/!\[.*?\]\(.*?\)/g;return e.replace(r,t=>`${t}
2
- `)}const He=`
3
-
1
+ (function(global, factory) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("react/jsx-runtime"), require("react"), require("@tiptap/extension-underline"), require("@tiptap/extension-text-style"), require("@tiptap/extension-color"), require("@tiptap/extension-bullet-list"), require("@tiptap/extension-highlight"), require("@tiptap/react"), require("@radix-ui/react-slot"), require("@firecms/ui"), require("@tiptap/starter-kit"), require("@tiptap/extension-horizontal-rule"), require("@tiptap/extension-link"), require("@tiptap/extension-image"), require("@tiptap/extension-placeholder"), require("@tiptap/extension-task-item"), require("@tiptap/extension-task-list"), require("@tiptap/core"), require("prosemirror-state"), require("prosemirror-view"), require("tiptap-markdown"), require("@tiptap/pm/view"), require("@tiptap/pm/state"), require("@tiptap/extension-document"), require("@tiptap/suggestion"), require("tippy.js")) : typeof define === "function" && define.amd ? define(["exports", "react/jsx-runtime", "react", "@tiptap/extension-underline", "@tiptap/extension-text-style", "@tiptap/extension-color", "@tiptap/extension-bullet-list", "@tiptap/extension-highlight", "@tiptap/react", "@radix-ui/react-slot", "@firecms/ui", "@tiptap/starter-kit", "@tiptap/extension-horizontal-rule", "@tiptap/extension-link", "@tiptap/extension-image", "@tiptap/extension-placeholder", "@tiptap/extension-task-item", "@tiptap/extension-task-list", "@tiptap/core", "prosemirror-state", "prosemirror-view", "tiptap-markdown", "@tiptap/pm/view", "@tiptap/pm/state", "@tiptap/extension-document", "@tiptap/suggestion", "tippy.js"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["FireCMS Editor"] = {}, global.jsxRuntime, global.React, global.extensionUnderline, global.TextStyle, global.extensionColor, global.BulletList, global.Highlight, global.react, global.reactSlot, global.ui, global.StarterKit, global.HorizontalRule, global.TiptapLink, global.TiptapImage, global.Placeholder, global.extensionTaskItem, global.extensionTaskList, global.core, global.prosemirrorState, global.prosemirrorView, global.tiptapMarkdown, global.view, global.state, global.Document, global.Suggestion, global.tippy));
3
+ })(this, function(exports2, jsxRuntime, React, extensionUnderline, TextStyle, extensionColor, BulletList, Highlight, react, reactSlot, ui, StarterKit, HorizontalRule, TiptapLink, TiptapImage, Placeholder, extensionTaskItem, extensionTaskList, core, prosemirrorState, prosemirrorView, tiptapMarkdown, view, state, Document, Suggestion, tippy) {
4
+ "use strict";
5
+ const EditorBubble = React.forwardRef(
6
+ ({ children, tippyOptions, ...rest }, ref) => {
7
+ const { editor } = react.useCurrentEditor();
8
+ const instanceRef = React.useRef(null);
9
+ React.useEffect(() => {
10
+ if (!instanceRef.current || !tippyOptions?.placement) return;
11
+ instanceRef.current.setProps({ placement: tippyOptions.placement });
12
+ instanceRef.current.popperInstance?.update();
13
+ }, [tippyOptions?.placement]);
14
+ const bubbleMenuProps = React.useMemo(() => {
15
+ const shouldShow = ({ editor: editor2, state: state2 }) => {
16
+ const { selection } = state2;
17
+ const { empty } = selection;
18
+ if (editor2.isActive("image") || empty || react.isNodeSelection(selection)) {
19
+ return false;
20
+ }
21
+ return true;
22
+ };
23
+ return {
24
+ shouldShow,
25
+ tippyOptions: {
26
+ onCreate: (val) => {
27
+ instanceRef.current = val;
28
+ },
29
+ moveTransition: "transform 0.15s ease-out",
30
+ ...tippyOptions
31
+ },
32
+ ...rest
33
+ };
34
+ }, [rest, tippyOptions]);
35
+ if (!editor) return null;
36
+ return (
37
+ //We need to add this because of https://github.com/ueberdosis/tiptap/issues/2658
38
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref, children: /* @__PURE__ */ jsxRuntime.jsx(react.BubbleMenu, { editor, ...bubbleMenuProps, children }) })
39
+ );
40
+ }
41
+ );
42
+ const EditorBubbleItem = React.forwardRef(({ children, asChild, onSelect, ...rest }, ref) => {
43
+ const { editor } = react.useCurrentEditor();
44
+ const Comp = asChild ? reactSlot.Slot : "div";
45
+ if (!editor) return null;
46
+ return /* @__PURE__ */ jsxRuntime.jsx(Comp, { ref, ...rest, onClick: () => onSelect?.(editor), children });
47
+ });
48
+ EditorBubbleItem.displayName = "EditorBubbleItem";
49
+ const items = [
50
+ {
51
+ name: "Text",
52
+ icon: ui.TextFieldsIcon,
53
+ command: (editor) => editor?.chain().focus().toggleNode("paragraph", "paragraph").run(),
54
+ // I feel like there has to be a more efficient way to do this – feel free to PR if you know how!
55
+ isActive: (editor) => (editor?.isActive("paragraph") && !editor?.isActive("bulletList") && !editor?.isActive("orderedList")) ?? false
56
+ },
57
+ {
58
+ name: "Heading 1",
59
+ icon: ui.LooksOneIcon,
60
+ command: (editor) => editor?.chain().focus().toggleHeading({ level: 1 }).run(),
61
+ isActive: (editor) => editor?.isActive("heading", { level: 1 }) ?? false
62
+ },
63
+ {
64
+ name: "Heading 2",
65
+ icon: ui.LooksTwoIcon,
66
+ command: (editor) => editor?.chain().focus().toggleHeading({ level: 2 }).run(),
67
+ isActive: (editor) => editor?.isActive("heading", { level: 2 }) ?? false
68
+ },
69
+ {
70
+ name: "Heading 3",
71
+ icon: ui.Looks3Icon,
72
+ command: (editor) => editor?.chain().focus().toggleHeading({ level: 3 }).run(),
73
+ isActive: (editor) => editor?.isActive("heading", { level: 3 }) ?? false
74
+ },
75
+ {
76
+ name: "To-do List",
77
+ icon: ui.CheckBoxIcon,
78
+ command: (editor) => editor?.chain().focus().toggleTaskList().run(),
79
+ isActive: (editor) => editor?.isActive("taskItem") ?? false
80
+ },
81
+ {
82
+ name: "Bullet List",
83
+ icon: ui.FormatListBulletedIcon,
84
+ command: (editor) => editor?.chain().focus().toggleBulletList().run(),
85
+ isActive: (editor) => editor?.isActive("bulletList") ?? false
86
+ },
87
+ {
88
+ name: "Numbered List",
89
+ icon: ui.FormatListNumberedIcon,
90
+ command: (editor) => editor?.chain().focus().toggleOrderedList().run(),
91
+ isActive: (editor) => editor?.isActive("orderedList") ?? false
92
+ },
93
+ {
94
+ name: "Quote",
95
+ icon: ui.FormatQuoteIcon,
96
+ command: (editor) => editor?.chain().focus().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
97
+ isActive: (editor) => editor?.isActive("blockquote") ?? false
98
+ },
99
+ {
100
+ name: "Code",
101
+ icon: ui.CodeIcon,
102
+ command: (editor) => editor?.chain().focus().toggleCodeBlock().run(),
103
+ isActive: (editor) => editor?.isActive("codeBlock") ?? false
104
+ }
105
+ ];
106
+ const NodeSelector = ({
107
+ open,
108
+ onOpenChange,
109
+ portalContainer
110
+ }) => {
111
+ const { editor } = react.useCurrentEditor();
112
+ if (!editor) return null;
113
+ const activeItem = items.filter((item) => item.isActive(editor)).pop() ?? {
114
+ name: "Multiple"
115
+ };
116
+ return /* @__PURE__ */ jsxRuntime.jsx(
117
+ ui.Popover,
118
+ {
119
+ sideOffset: 5,
120
+ align: "start",
121
+ portalContainer,
122
+ className: "w-48 p-1",
123
+ trigger: /* @__PURE__ */ jsxRuntime.jsxs(
124
+ ui.Button,
125
+ {
126
+ variant: "text",
127
+ className: "gap-2 rounded-none",
128
+ color: "text",
129
+ children: [
130
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "whitespace-nowrap text-sm", children: activeItem.name }),
131
+ /* @__PURE__ */ jsxRuntime.jsx(ui.ExpandMoreIcon, { size: "small" })
132
+ ]
133
+ }
134
+ ),
135
+ modal: true,
136
+ open,
137
+ onOpenChange,
138
+ children: items.map((item, index) => /* @__PURE__ */ jsxRuntime.jsxs(
139
+ EditorBubbleItem,
140
+ {
141
+ onSelect: (editor2) => {
142
+ item.command(editor2);
143
+ onOpenChange(false);
144
+ },
145
+ className: "flex cursor-pointer items-center justify-between rounded px-2 py-1 text-sm hover:bg-blue-50 hover:dark:bg-gray-700 text-gray-900 dark:text-white",
146
+ children: [
147
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [
148
+ /* @__PURE__ */ jsxRuntime.jsx(item.icon, { size: "smallest" }),
149
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: item.name })
150
+ ] }),
151
+ activeItem.name === item.name && /* @__PURE__ */ jsxRuntime.jsx(ui.CheckIcon, { size: "smallest" })
152
+ ]
153
+ },
154
+ index
155
+ ))
156
+ }
157
+ );
158
+ };
159
+ function isValidUrl(url) {
160
+ try {
161
+ new URL(url);
162
+ return true;
163
+ } catch (e) {
164
+ return false;
165
+ }
166
+ }
167
+ function getUrlFromString(str) {
168
+ if (isValidUrl(str)) return str;
169
+ try {
170
+ if (str.includes(".") && !str.includes(" ")) {
171
+ return new URL(`https://${str}`).toString();
172
+ }
173
+ return null;
174
+ } catch (e) {
175
+ return null;
176
+ }
177
+ }
178
+ const LinkSelector = ({
179
+ open,
180
+ onOpenChange
181
+ }) => {
182
+ const inputRef = React.useRef(null);
183
+ const { editor } = react.useCurrentEditor();
184
+ React.useEffect(() => {
185
+ inputRef.current && inputRef.current?.focus();
186
+ });
187
+ if (!editor) return null;
188
+ return /* @__PURE__ */ jsxRuntime.jsx(
189
+ ui.Popover,
190
+ {
191
+ modal: true,
192
+ open,
193
+ onOpenChange,
194
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(
195
+ ui.Button,
196
+ {
197
+ variant: "text",
198
+ className: "gap-2 rounded-none",
199
+ color: "text",
200
+ children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: ui.cls("underline decoration-stone-400 underline-offset-4", {
201
+ "text-blue-500": editor.isActive("link")
202
+ }), children: "Link" })
203
+ }
204
+ ),
205
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
206
+ "form",
207
+ {
208
+ onSubmit: (e) => {
209
+ const target = e.currentTarget;
210
+ e.preventDefault();
211
+ const input = target[0];
212
+ const url = getUrlFromString(input.value);
213
+ url && editor.chain().focus().setLink({ href: url }).run();
214
+ },
215
+ className: "flex p-1",
216
+ children: [
217
+ /* @__PURE__ */ jsxRuntime.jsx(
218
+ "input",
219
+ {
220
+ ref: inputRef,
221
+ autoFocus: open,
222
+ placeholder: "Paste a link",
223
+ defaultValue: editor.getAttributes("link").href || "",
224
+ className: ui.cls("text-gray-900 dark:text-white flex-grow bg-transparent p-1 text-sm outline-none", ui.focusedDisabled)
225
+ }
226
+ ),
227
+ editor.getAttributes("link").href ? /* @__PURE__ */ jsxRuntime.jsx(
228
+ ui.Button,
229
+ {
230
+ size: "small",
231
+ variant: "text",
232
+ type: "button",
233
+ color: "text",
234
+ className: "flex items-center",
235
+ onClick: () => {
236
+ editor.chain().focus().unsetLink().run();
237
+ },
238
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.DeleteIcon, { size: "small" })
239
+ }
240
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
241
+ ui.Button,
242
+ {
243
+ size: "small",
244
+ variant: "text",
245
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.CheckIcon, { size: "small" })
246
+ }
247
+ )
248
+ ]
249
+ }
250
+ )
251
+ }
252
+ );
253
+ };
254
+ const TextButtons = () => {
255
+ const { editor } = react.useCurrentEditor();
256
+ if (!editor) return null;
257
+ const items2 = [
258
+ {
259
+ name: "bold",
260
+ isActive: (editor2) => editor2?.isActive("bold") ?? false,
261
+ command: (editor2) => editor2?.chain().focus().toggleBold().run(),
262
+ icon: ui.FormatBoldIcon
263
+ },
264
+ {
265
+ name: "italic",
266
+ isActive: (editor2) => editor2?.isActive("italic") ?? false,
267
+ command: (editor2) => editor2?.chain().focus().toggleItalic().run(),
268
+ icon: ui.FormatItalicIcon
269
+ },
270
+ {
271
+ name: "underline",
272
+ isActive: (editor2) => editor2?.isActive("underline") ?? false,
273
+ command: (editor2) => editor2?.chain().focus().toggleUnderline().run(),
274
+ icon: ui.FormatUnderlinedIcon
275
+ },
276
+ {
277
+ name: "strike",
278
+ isActive: (editor2) => editor2?.isActive("strike") ?? false,
279
+ command: (editor2) => editor2?.chain().focus().toggleStrike().run(),
280
+ icon: ui.FormatStrikethroughIcon
281
+ },
282
+ {
283
+ name: "code",
284
+ isActive: (editor2) => editor2?.isActive("code") ?? false,
285
+ command: (editor2) => editor2?.chain().focus().toggleCode().run(),
286
+ icon: ui.CodeIcon
287
+ }
288
+ ];
289
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex", children: items2.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
290
+ EditorBubbleItem,
291
+ {
292
+ onSelect: (editor2) => {
293
+ item.command(editor2);
294
+ },
295
+ children: /* @__PURE__ */ jsxRuntime.jsx(
296
+ ui.Button,
297
+ {
298
+ size: "small",
299
+ color: "text",
300
+ className: "gap-2 rounded-none h-full",
301
+ variant: "text",
302
+ children: /* @__PURE__ */ jsxRuntime.jsx(
303
+ item.icon,
304
+ {
305
+ className: ui.cls({
306
+ "text-inherit": !item.isActive(editor),
307
+ "text-blue-500": item.isActive(editor)
308
+ })
309
+ }
310
+ )
311
+ }
312
+ )
313
+ },
314
+ index
315
+ )) });
316
+ };
317
+ function removeClassesFromJson(jsonObj) {
318
+ if (Array.isArray(jsonObj)) {
319
+ return jsonObj.map((item) => removeClassesFromJson(item));
320
+ } else if (typeof jsonObj === "object" && jsonObj !== null) {
321
+ if (jsonObj.attrs && typeof jsonObj.attrs === "object" && "class" in jsonObj.attrs) {
322
+ delete jsonObj.attrs.class;
323
+ }
324
+ Object.keys(jsonObj).forEach((key) => {
325
+ jsonObj[key] = removeClassesFromJson(jsonObj[key]);
326
+ });
327
+ }
328
+ return jsonObj;
329
+ }
330
+ const loadingDecorationKey = new prosemirrorState.PluginKey("loadingDecoration");
331
+ const TextLoadingDecorationExtension = core.Extension.create({
332
+ name: "loadingDecoration",
333
+ addOptions() {
334
+ return {
335
+ pluginKey: loadingDecorationKey
336
+ };
337
+ },
338
+ addProseMirrorPlugins() {
339
+ const pluginKey = this.options.pluginKey;
340
+ return [
341
+ new prosemirrorState.Plugin({
342
+ key: pluginKey,
343
+ state: {
344
+ init() {
345
+ return {
346
+ decorationSet: prosemirrorView.DecorationSet.empty,
347
+ hasDecoration: false
348
+ };
349
+ },
350
+ apply(tr, oldState) {
351
+ const action = tr.getMeta(pluginKey);
352
+ if (action?.type === "loadingDecoration") {
353
+ const { pos, remove, loadingHtml } = action;
354
+ if (remove) {
355
+ return {
356
+ decorationSet: prosemirrorView.DecorationSet.empty,
357
+ hasDecoration: false
358
+ };
359
+ }
360
+ const decoration = prosemirrorView.Decoration.widget(pos, () => {
361
+ const container = document.createElement("span");
362
+ container.className = "loading-decoration";
363
+ if (loadingHtml) {
364
+ container.innerHTML = loadingHtml;
365
+ } else {
366
+ const span = document.createElement("span");
367
+ span.innerText = "loading...";
368
+ container.appendChild(span);
369
+ }
370
+ return container;
371
+ });
372
+ return {
373
+ decorationSet: prosemirrorView.DecorationSet.empty.add(tr.doc, [decoration]),
374
+ hasDecoration: true
375
+ };
376
+ }
377
+ return {
378
+ decorationSet: oldState.decorationSet.map(tr.mapping, tr.doc),
379
+ hasDecoration: oldState.hasDecoration
380
+ };
381
+ }
382
+ },
383
+ props: {
384
+ decorations(state2) {
385
+ return this.getState(state2)?.decorationSet || prosemirrorView.DecorationSet.empty;
386
+ }
387
+ }
388
+ })
389
+ ];
390
+ },
391
+ addCommands() {
392
+ return {
393
+ toggleLoadingDecoration: (loadingHtml) => ({ state: state2, dispatch }) => {
394
+ const { selection } = state2;
395
+ const pos = selection.from;
396
+ if (!dispatch) return false;
397
+ const pluginKey = this.options.pluginKey;
398
+ const tr = state2.tr.setMeta(pluginKey, {
399
+ pos,
400
+ type: "loadingDecoration",
401
+ remove: false,
402
+ loadingHtml
403
+ });
404
+ dispatch(tr);
405
+ return true;
406
+ },
407
+ removeLoadingDecoration: () => ({ state: state2, dispatch }) => {
408
+ if (!dispatch) return false;
409
+ const pluginKey = this.options.pluginKey;
410
+ const tr = state2.tr.setMeta(pluginKey, {
411
+ pos: 0,
412
+ // We can pass any position as it will remove the entire decoration set
413
+ type: "loadingDecoration",
414
+ remove: true
415
+ });
416
+ dispatch(tr);
417
+ return true;
418
+ }
419
+ };
420
+ }
421
+ });
422
+ const PlaceholderExtension = Placeholder.configure({
423
+ placeholder: ({
424
+ node,
425
+ editor
426
+ }) => {
427
+ editor.state.selection;
428
+ function hasLoadingDecoration(editor2) {
429
+ const pluginState = loadingDecorationKey.get(editor2.state);
430
+ return pluginState?.getState(editor2.state)?.hasDecoration ?? false;
431
+ }
432
+ const hasDecoration = hasLoadingDecoration(editor);
433
+ if (hasDecoration) {
434
+ return "";
435
+ }
436
+ if (node.type.name === "heading") {
437
+ return `Heading ${node.attrs.level}`;
438
+ }
439
+ return "Press '/' for commands";
440
+ },
441
+ includeChildren: true
442
+ });
443
+ const Horizontal = HorizontalRule.extend({
444
+ addInputRules() {
445
+ return [
446
+ new core.InputRule({
447
+ find: /^(?:---|—-|___\s|\*\*\*\s)$/,
448
+ handler: ({
449
+ state: state2,
450
+ range
451
+ }) => {
452
+ const attributes = {};
453
+ const { tr } = state2;
454
+ const start = range.from;
455
+ const end = range.to;
456
+ tr.insert(start - 1, this.type.create(attributes)).delete(
457
+ tr.mapping.map(start),
458
+ tr.mapping.map(end)
459
+ );
460
+ }
461
+ })
462
+ ];
463
+ }
464
+ });
465
+ const placeholder = PlaceholderExtension;
466
+ const tiptapLink = TiptapLink.configure({
467
+ HTMLAttributes: {
468
+ class: ui.cls(
469
+ "text-gray-600 dark:text-slate-300 underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer"
470
+ )
471
+ }
472
+ });
473
+ const taskList = extensionTaskList.TaskList.configure({
474
+ HTMLAttributes: {
475
+ class: ui.cls("not-prose")
476
+ }
477
+ });
478
+ const taskItem = extensionTaskItem.TaskItem.configure({
479
+ HTMLAttributes: {
480
+ class: ui.cls("flex items-start my-4")
481
+ },
482
+ nested: true
483
+ });
484
+ const markdownExtension = tiptapMarkdown.Markdown.configure({
485
+ html: true
486
+ });
487
+ const horizontalRule = Horizontal.configure({
488
+ HTMLAttributes: {
489
+ class: ui.cls("mt-4 mb-6 border-t", ui.defaultBorderMixin)
490
+ }
491
+ });
492
+ const starterKit = StarterKit.configure({
493
+ bulletList: {
494
+ HTMLAttributes: {
495
+ class: ui.cls("list-disc list-outside leading-3 -mt-2")
496
+ }
497
+ },
498
+ orderedList: {
499
+ HTMLAttributes: {
500
+ class: ui.cls("list-decimal list-outside leading-3 -mt-2")
501
+ }
502
+ },
503
+ listItem: {
504
+ HTMLAttributes: {
505
+ class: ui.cls("leading-normal -mb-2")
506
+ }
507
+ },
508
+ blockquote: {
509
+ HTMLAttributes: {
510
+ class: ui.cls("border-l-4 border-primary")
511
+ }
512
+ },
513
+ codeBlock: {
514
+ HTMLAttributes: {
515
+ class: ui.cls("rounded bg-blue-50 dark:bg-gray-700 border p-5 font-mono font-medium", ui.defaultBorderMixin)
516
+ }
517
+ },
518
+ code: {
519
+ HTMLAttributes: {
520
+ class: ui.cls("rounded-md bg-slate-50 dark:bg-gray-700 px-1.5 py-1 font-mono font-medium"),
521
+ spellcheck: "false"
522
+ }
523
+ },
524
+ document: false,
525
+ horizontalRule: false,
526
+ dropcursor: {
527
+ color: "#DBEAFE",
528
+ width: 4
529
+ },
530
+ gapcursor: false
531
+ });
532
+ async function onFileRead(view$1, readerEvent, pos, upload, image) {
533
+ const { schema } = view$1.state;
534
+ const plugin = view$1.state.plugins.find((p) => p.key === ImagePluginKey.key);
535
+ if (!plugin) {
536
+ console.error("Image plugin not found");
537
+ return;
538
+ }
539
+ let decorationSet = plugin.getState(view$1.state);
540
+ const placeholder2 = document.createElement("div");
541
+ const imageElement = document.createElement("img");
542
+ imageElement.setAttribute("class", "opacity-40 rounded-lg border " + ui.defaultBorderMixin);
543
+ imageElement.src = readerEvent.target?.result;
544
+ placeholder2.appendChild(imageElement);
545
+ const deco = view.Decoration.widget(pos, placeholder2);
546
+ decorationSet = decorationSet?.add(view$1.state.doc, [deco]);
547
+ view$1.dispatch(view$1.state.tr.setMeta(plugin, { decorationSet }));
548
+ const src = await upload(image);
549
+ console.debug("Uploaded image", src);
550
+ const imageNode = schema.nodes.image.create({ src });
551
+ const tr = view$1.state.tr.replaceWith(pos, pos, imageNode);
552
+ decorationSet = decorationSet?.remove([deco]);
553
+ tr.setMeta(plugin, { decorationSet });
554
+ view$1.dispatch(tr);
555
+ }
556
+ const ImagePluginKey = new state.PluginKey("imagePlugin");
557
+ const createDropImagePlugin = (upload) => {
558
+ const plugin = new state.Plugin({
559
+ key: ImagePluginKey,
560
+ state: {
561
+ // Initialize the plugin state with an empty DecorationSet
562
+ init: () => view.DecorationSet.empty,
563
+ // Apply transactions to update the state
564
+ apply: (tr, old) => {
565
+ const meta = tr.getMeta(plugin);
566
+ if (meta && meta.decorationSet) {
567
+ return meta.decorationSet;
568
+ }
569
+ return old.map(tr.mapping, tr.doc);
570
+ }
571
+ },
572
+ props: {
573
+ handleDOMEvents: {
574
+ drop: (view2, event) => {
575
+ if (!event.dataTransfer?.files || event.dataTransfer?.files.length === 0) {
576
+ return false;
577
+ }
578
+ event.preventDefault();
579
+ const files = Array.from(event.dataTransfer.files);
580
+ const images = files.filter((file) => /image/i.test(file.type));
581
+ if (images.length === 0) {
582
+ console.log("No images found in dropped files");
583
+ return false;
584
+ }
585
+ images.forEach((image) => {
586
+ const position = view2.posAtCoords({
587
+ left: event.clientX,
588
+ top: event.clientY
589
+ });
590
+ if (!position) return;
591
+ const reader = new FileReader();
592
+ reader.onload = async (readerEvent) => {
593
+ await onFileRead(view2, readerEvent, position.pos, upload, image);
594
+ };
595
+ reader.readAsDataURL(image);
596
+ });
597
+ return true;
598
+ }
599
+ },
600
+ handlePaste(view2, event, slice) {
601
+ const items2 = Array.from(event.clipboardData?.items || []);
602
+ const pos = view2.state.selection.from;
603
+ let anyImageFound = false;
604
+ items2.forEach((item) => {
605
+ const image = item.getAsFile();
606
+ if (image) {
607
+ anyImageFound = true;
608
+ const reader = new FileReader();
609
+ reader.onload = async (readerEvent) => {
610
+ await onFileRead(view2, readerEvent, pos, upload, image);
611
+ };
612
+ reader.readAsDataURL(image);
613
+ }
614
+ });
615
+ return anyImageFound;
616
+ },
617
+ decorations(state2) {
618
+ return plugin.getState(state2);
619
+ }
620
+ },
621
+ view(editorView) {
622
+ return {
623
+ update(view2, prevState) {
624
+ const prevDecos = plugin.getState(prevState);
625
+ const newDecos = plugin.getState(view2.state);
626
+ if (prevDecos !== newDecos) {
627
+ view2.updateState(view2.state);
628
+ }
629
+ }
630
+ };
631
+ }
632
+ });
633
+ return plugin;
634
+ };
635
+ const createImageExtension = (dropImagePlugin) => {
636
+ return TiptapImage.extend({
637
+ addProseMirrorPlugins() {
638
+ return [dropImagePlugin];
639
+ }
640
+ }).configure({
641
+ allowBase64: true,
642
+ HTMLAttributes: {
643
+ class: ui.cls("rounded-lg border", ui.defaultBorderMixin)
644
+ }
645
+ });
646
+ };
647
+ const CustomKeymap = core.Extension.create({
648
+ name: "CustomKeymap",
649
+ addCommands() {
650
+ return {
651
+ selectTextWithinNodeBoundaries: () => ({ editor, commands }) => {
652
+ const { state: state2 } = editor;
653
+ const { tr } = state2;
654
+ const startNodePos = tr.selection.$from.start();
655
+ const endNodePos = tr.selection.$to.end();
656
+ return commands.setTextSelection({
657
+ from: startNodePos,
658
+ to: endNodePos
659
+ });
660
+ }
661
+ };
662
+ },
663
+ addKeyboardShortcuts() {
664
+ return {
665
+ "Mod-a": ({ editor }) => {
666
+ const { state: state2 } = editor;
667
+ const { tr } = state2;
668
+ const startSelectionPos = tr.selection.from;
669
+ const endSelectionPos = tr.selection.to;
670
+ const startNodePos = tr.selection.$from.start();
671
+ const endNodePos = tr.selection.$to.end();
672
+ const isCurrentTextSelectionNotExtendedToNodeBoundaries = startSelectionPos > startNodePos || endSelectionPos < endNodePos;
673
+ if (isCurrentTextSelectionNotExtendedToNodeBoundaries) {
674
+ editor.chain().selectTextWithinNodeBoundaries().run();
675
+ return true;
676
+ }
677
+ return false;
678
+ }
679
+ };
680
+ }
681
+ });
682
+ function absoluteRect(element) {
683
+ const data = element.getBoundingClientRect();
684
+ let ancestor = element.parentElement;
685
+ while (ancestor && window.getComputedStyle(ancestor).position === "static") {
686
+ ancestor = ancestor.parentElement;
687
+ }
688
+ const ancestorRect = ancestor?.getBoundingClientRect();
689
+ return {
690
+ top: data.top - (ancestorRect?.top ?? 0),
691
+ left: data.left - (ancestorRect?.left ?? 0),
692
+ width: data.width
693
+ };
694
+ }
695
+ function nodeDOMAtCoords(coords) {
696
+ return document.elementsFromPoint(coords.x, coords.y).find(
697
+ (elem) => elem.parentElement?.matches?.(".ProseMirror") || elem.matches(
698
+ ["li", "p:not(:first-child)", "pre", "blockquote", "h1, h2, h3, h4, h5, h6"].join(", ")
699
+ )
700
+ );
701
+ }
702
+ function nodePosAtDOM(node, view2, options) {
703
+ const boundingRect = node.getBoundingClientRect();
704
+ return view2.posAtCoords({
705
+ left: boundingRect.left + 50 + options.dragHandleWidth,
706
+ top: boundingRect.top + 1
707
+ })?.inside;
708
+ }
709
+ function DragHandle(options) {
710
+ function handleDragStart(event, view$1) {
711
+ view$1.focus();
712
+ if (!event.dataTransfer) return;
713
+ const node = nodeDOMAtCoords({
714
+ x: event.clientX + 50 + options.dragHandleWidth,
715
+ y: event.clientY
716
+ });
717
+ if (!(node instanceof Element)) return;
718
+ const nodePos = nodePosAtDOM(node, view$1, options);
719
+ if (nodePos == null || nodePos < 0) return;
720
+ view$1.dispatch(view$1.state.tr.setSelection(state.NodeSelection.create(view$1.state.doc, nodePos)));
721
+ const slice = view$1.state.selection.content();
722
+ const {
723
+ dom,
724
+ text
725
+ } = view.__serializeForClipboard(view$1, slice);
726
+ event.dataTransfer.clearData();
727
+ event.dataTransfer.setData("text/html", dom.innerHTML);
728
+ event.dataTransfer.setData("text/plain", text);
729
+ event.dataTransfer.effectAllowed = "copyMove";
730
+ event.dataTransfer.setDragImage(node, 0, 0);
731
+ view$1.dragging = {
732
+ slice,
733
+ move: event.ctrlKey
734
+ };
735
+ }
736
+ function handleClick(event, view2) {
737
+ view2.focus();
738
+ view2.dom.classList.remove("dragging");
739
+ const node = nodeDOMAtCoords({
740
+ x: event.clientX + 50 + options.dragHandleWidth,
741
+ y: event.clientY
742
+ });
743
+ if (!(node instanceof Element)) return;
744
+ const nodePos = nodePosAtDOM(node, view2, options);
745
+ if (!nodePos) return;
746
+ view2.dispatch(view2.state.tr.setSelection(state.NodeSelection.create(view2.state.doc, nodePos)));
747
+ }
748
+ let dragHandleElement = null;
749
+ function hideDragHandle() {
750
+ if (dragHandleElement) {
751
+ dragHandleElement.classList.add("hide");
752
+ }
753
+ }
754
+ function showDragHandle() {
755
+ if (dragHandleElement) {
756
+ dragHandleElement.classList.remove("hide");
757
+ }
758
+ }
759
+ return new state.Plugin({
760
+ view: (view2) => {
761
+ dragHandleElement = document.createElement("div");
762
+ dragHandleElement.draggable = true;
763
+ dragHandleElement.dataset.dragHandle = "";
764
+ dragHandleElement.classList.add("drag-handle");
765
+ dragHandleElement.addEventListener("dragstart", (e) => {
766
+ handleDragStart(e, view2);
767
+ });
768
+ dragHandleElement.addEventListener("click", (e) => {
769
+ handleClick(e, view2);
770
+ });
771
+ hideDragHandle();
772
+ view2?.dom?.parentElement?.appendChild(dragHandleElement);
773
+ return {
774
+ destroy: () => {
775
+ }
776
+ };
777
+ },
778
+ props: {
779
+ handleDOMEvents: {
780
+ mousemove: (view2, event) => {
781
+ if (!view2.editable) {
782
+ return;
783
+ }
784
+ const node = nodeDOMAtCoords({
785
+ x: event.clientX + 50 + options.dragHandleWidth,
786
+ y: event.clientY
787
+ });
788
+ if (!(node instanceof Element)) {
789
+ hideDragHandle();
790
+ return;
791
+ }
792
+ const compStyle = window.getComputedStyle(node);
793
+ const lineHeight = parseInt(compStyle.lineHeight, 10);
794
+ const paddingTop = parseInt(compStyle.paddingTop, 10);
795
+ const rect = absoluteRect(node);
796
+ rect.top += (lineHeight - 24) / 2;
797
+ rect.top += paddingTop;
798
+ if (node.matches("ul:not([data-type=taskList]) li, ol li")) {
799
+ rect.left -= options.dragHandleWidth;
800
+ }
801
+ rect.width = options.dragHandleWidth;
802
+ if (!dragHandleElement) return;
803
+ dragHandleElement.style.left = `${rect.left - rect.width}px`;
804
+ dragHandleElement.style.top = `${rect.top}px`;
805
+ showDragHandle();
806
+ },
807
+ keydown: () => {
808
+ hideDragHandle();
809
+ },
810
+ mousewheel: () => {
811
+ hideDragHandle();
812
+ },
813
+ // dragging class is used for CSS
814
+ dragstart: (view2) => {
815
+ view2.dom.classList.add("dragging");
816
+ },
817
+ drop: (view2) => {
818
+ view2.dom.classList.remove("dragging");
819
+ },
820
+ dragend: (view2) => {
821
+ view2.dom.classList.remove("dragging");
822
+ }
823
+ }
824
+ }
825
+ });
826
+ }
827
+ const DragAndDrop = core.Extension.create({
828
+ name: "dragAndDrop",
829
+ addProseMirrorPlugins() {
830
+ return [
831
+ DragHandle({
832
+ dragHandleWidth: 24
833
+ })
834
+ ];
835
+ }
836
+ });
837
+ const CommandPluginKey = new state.PluginKey("slash-command");
838
+ const SlashCommand = react.Node.create({
839
+ name: "command",
840
+ addOptions() {
841
+ return {
842
+ HTMLAttributes: {},
843
+ renderText({
844
+ options,
845
+ node
846
+ }) {
847
+ return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`;
848
+ },
849
+ deleteTriggerWithBackspace: false,
850
+ renderHTML({
851
+ options,
852
+ node
853
+ }) {
854
+ return [
855
+ "span",
856
+ react.mergeAttributes(this.HTMLAttributes, options.HTMLAttributes),
857
+ `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`
858
+ ];
859
+ },
860
+ suggestion: {
861
+ char: "/",
862
+ pluginKey: CommandPluginKey,
863
+ command: ({
864
+ editor,
865
+ range,
866
+ props
867
+ }) => {
868
+ const nodeAfter = editor.view.state.selection.$to.nodeAfter;
869
+ const overrideSpace = nodeAfter?.text?.startsWith(" ");
870
+ if (overrideSpace) {
871
+ range.to += 1;
872
+ }
873
+ editor.chain().focus().insertContentAt(range, [
874
+ {
875
+ type: this.name,
876
+ attrs: props
877
+ },
878
+ {
879
+ type: "text",
880
+ text: " "
881
+ }
882
+ ]).run();
883
+ window.getSelection()?.collapseToEnd();
884
+ },
885
+ allow: ({
886
+ state: state2,
887
+ range
888
+ }) => {
889
+ const $from = state2.doc.resolve(range.from);
890
+ const type = state2.schema.nodes[this.name];
891
+ const allow = !!$from.parent.type.contentMatch.matchType(type);
892
+ return allow;
893
+ }
894
+ }
895
+ };
896
+ },
897
+ group: "inline",
898
+ inline: true,
899
+ selectable: false,
900
+ atom: true,
901
+ addAttributes() {
902
+ return {
903
+ id: {
904
+ default: null,
905
+ parseHTML: (element) => element.getAttribute("data-id"),
906
+ renderHTML: (attributes) => {
907
+ if (!attributes.id) {
908
+ return {};
909
+ }
910
+ return {
911
+ "data-id": attributes.id
912
+ };
913
+ }
914
+ },
915
+ label: {
916
+ default: null,
917
+ parseHTML: (element) => element.getAttribute("data-label"),
918
+ renderHTML: (attributes) => {
919
+ if (!attributes.label) {
920
+ return {};
921
+ }
922
+ return {
923
+ "data-label": attributes.label
924
+ };
925
+ }
926
+ }
927
+ };
928
+ },
929
+ parseHTML() {
930
+ return [
931
+ {
932
+ tag: `span[data-type="${this.name}"]`
933
+ }
934
+ ];
935
+ },
936
+ renderHTML({
937
+ node,
938
+ HTMLAttributes
939
+ }) {
940
+ if (this.options.renderLabel !== void 0) {
941
+ console.warn("renderLabel is deprecated use renderText and renderHTML instead");
942
+ return [
943
+ "span",
944
+ react.mergeAttributes({ "data-type": this.name }, this.options.HTMLAttributes, HTMLAttributes),
945
+ this.options.renderLabel({
946
+ options: this.options,
947
+ node
948
+ })
949
+ ];
950
+ }
951
+ const mergedOptions = { ...this.options };
952
+ mergedOptions.HTMLAttributes = react.mergeAttributes({ "data-type": this.name }, this.options.HTMLAttributes, HTMLAttributes);
953
+ const html = this.options.renderHTML({
954
+ options: mergedOptions,
955
+ node
956
+ });
957
+ if (typeof html === "string") {
958
+ return [
959
+ "span",
960
+ react.mergeAttributes({ "data-type": this.name }, this.options.HTMLAttributes, HTMLAttributes),
961
+ html
962
+ ];
963
+ }
964
+ return html;
965
+ },
966
+ renderText({ node }) {
967
+ return this.options.renderText({
968
+ options: this.options,
969
+ node
970
+ });
971
+ },
972
+ addKeyboardShortcuts() {
973
+ return {
974
+ Backspace: () => this.editor.commands.command(({
975
+ tr,
976
+ state: state2
977
+ }) => {
978
+ let isCommand = false;
979
+ const { selection } = state2;
980
+ const {
981
+ empty,
982
+ anchor
983
+ } = selection;
984
+ if (!empty) {
985
+ return false;
986
+ }
987
+ state2.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {
988
+ if (node.type.name === this.name) {
989
+ isCommand = true;
990
+ tr.insertText(
991
+ this.options.deleteTriggerWithBackspace ? "" : this.options.suggestion.char || "",
992
+ pos,
993
+ pos + node.nodeSize
994
+ );
995
+ return false;
996
+ }
997
+ return true;
998
+ });
999
+ return isCommand;
1000
+ })
1001
+ };
1002
+ },
1003
+ addProseMirrorPlugins() {
1004
+ return [
1005
+ Suggestion({
1006
+ editor: this.editor,
1007
+ ...this.options.suggestion
1008
+ })
1009
+ ];
1010
+ }
1011
+ });
1012
+ const suggestion = (ref, {
1013
+ upload,
1014
+ onDisabledAutocompleteClick,
1015
+ aiController
1016
+ }) => ({
1017
+ items: ({ query }) => {
1018
+ const availableSuggestionItems = [...suggestionItems];
1019
+ if (!onDisabledAutocompleteClick && aiController) {
1020
+ availableSuggestionItems.push(autocompleteSuggestionItem);
1021
+ }
1022
+ if (onDisabledAutocompleteClick) {
1023
+ availableSuggestionItems.push({
1024
+ title: "Autocomplete",
1025
+ description: "Add text based on the context.",
1026
+ searchTerms: ["ai"],
1027
+ icon: /* @__PURE__ */ jsxRuntime.jsx(ui.AutoAwesomeIcon, { size: 18 }),
1028
+ command: onDisabledAutocompleteClick
1029
+ });
1030
+ }
1031
+ return availableSuggestionItems.filter((item) => {
1032
+ const inTitle = item.title.toLowerCase().startsWith(query.toLowerCase());
1033
+ if (inTitle) return inTitle;
1034
+ const inSearchTerms = item.searchTerms?.some((term) => term.toLowerCase().startsWith(query.toLowerCase()));
1035
+ return inSearchTerms;
1036
+ });
1037
+ },
1038
+ render: () => {
1039
+ let component;
1040
+ let popup;
1041
+ return {
1042
+ onStart: (props) => {
1043
+ component = new react.ReactRenderer(CommandList, {
1044
+ props: {
1045
+ ...props,
1046
+ upload,
1047
+ aiController
1048
+ },
1049
+ editor: props.editor
1050
+ });
1051
+ if (!props.clientRect) {
1052
+ return;
1053
+ }
1054
+ popup = tippy("body", {
1055
+ getReferenceClientRect: props.clientRect,
1056
+ appendTo: ref?.current,
1057
+ content: component.element,
1058
+ showOnCreate: true,
1059
+ interactive: true,
1060
+ trigger: "manual",
1061
+ placement: "bottom-start"
1062
+ });
1063
+ },
1064
+ onUpdate(props) {
1065
+ component.updateProps(props);
1066
+ if (!props.clientRect) {
1067
+ return;
1068
+ }
1069
+ popup[0].setProps({
1070
+ getReferenceClientRect: props.clientRect
1071
+ });
1072
+ },
1073
+ onKeyDown(props) {
1074
+ if (props.event.key === "Escape") {
1075
+ popup[0].hide();
1076
+ props.event.preventDefault();
1077
+ return true;
1078
+ }
1079
+ return component.ref?.onKeyDown(props);
1080
+ },
1081
+ onExit() {
1082
+ if (popup && popup[0])
1083
+ popup[0].destroy();
1084
+ component?.destroy();
1085
+ }
1086
+ };
1087
+ }
1088
+ });
1089
+ const CommandList = React.forwardRef((props, ref) => {
1090
+ const [selectedIndex, setSelectedIndex] = React.useState(0);
1091
+ const { editor } = react.useCurrentEditor();
1092
+ const selectItem = (item) => {
1093
+ if (!editor) return;
1094
+ item?.command?.({
1095
+ editor,
1096
+ range: props.range,
1097
+ upload: props.upload,
1098
+ aiController: props.aiController
1099
+ });
1100
+ };
1101
+ const upHandler = () => {
1102
+ setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length);
1103
+ };
1104
+ const downHandler = () => {
1105
+ setSelectedIndex((selectedIndex + 1) % props.items.length);
1106
+ };
1107
+ const enterHandler = () => {
1108
+ const item = props.items[selectedIndex];
1109
+ selectItem(item);
1110
+ };
1111
+ React.useEffect(() => setSelectedIndex(0), [props.items]);
1112
+ React.useImperativeHandle(ref, () => ({
1113
+ onKeyDown: ({ event }) => {
1114
+ if (event.key === "ArrowUp") {
1115
+ upHandler();
1116
+ return true;
1117
+ }
1118
+ if (event.key === "ArrowDown") {
1119
+ downHandler();
1120
+ return true;
1121
+ }
1122
+ if (event.key === "Enter") {
1123
+ enterHandler();
1124
+ return true;
1125
+ }
1126
+ return false;
1127
+ }
1128
+ }));
1129
+ const itemRefs = React.useRef([]);
1130
+ React.useEffect(() => {
1131
+ if (itemRefs.current[selectedIndex]) {
1132
+ itemRefs.current[selectedIndex].scrollIntoView({
1133
+ block: "nearest"
1134
+ });
1135
+ }
1136
+ }, [selectedIndex]);
1137
+ return /* @__PURE__ */ jsxRuntime.jsx(
1138
+ "div",
1139
+ {
1140
+ className: ui.cls("text-gray-900 dark:text-white z-50 max-h-[280px] h-auto w-72 overflow-y-auto rounded-md border bg-white dark:bg-gray-900 px-1 py-2 shadow transition-all", ui.defaultBorderMixin),
1141
+ children: props.items.length ? props.items.map((item, index) => /* @__PURE__ */ jsxRuntime.jsxs(
1142
+ "button",
1143
+ {
1144
+ value: item.title,
1145
+ ref: (el) => {
1146
+ if (!el) return;
1147
+ return itemRefs.current[index] = el;
1148
+ },
1149
+ onClick: () => selectItem(item),
1150
+ tabIndex: index === selectedIndex ? 0 : -1,
1151
+ "aria-selected": index === selectedIndex,
1152
+ className: ui.cls(
1153
+ "flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm hover:bg-blue-50 hover:dark:bg-gray-700 aria-selected:bg-blue-50 aria-selected:dark:bg-gray-700",
1154
+ index === selectedIndex ? "bg-blue-100 dark:bg-slate-950" : ""
1155
+ ),
1156
+ children: [
1157
+ /* @__PURE__ */ jsxRuntime.jsx(
1158
+ "div",
1159
+ {
1160
+ className: ui.cls("flex h-10 w-10 items-center justify-center rounded-md border bg-white dark:bg-gray-900", ui.defaultBorderMixin),
1161
+ children: item.icon
1162
+ }
1163
+ ),
1164
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1165
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: item.title }),
1166
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-700 dark:text-slate-300", children: item.description })
1167
+ ] })
1168
+ ]
1169
+ },
1170
+ item.title
1171
+ )) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "item", children: "No result" })
1172
+ }
1173
+ );
1174
+ });
1175
+ const autocompleteSuggestionItem = {
1176
+ title: "Autocomplete",
1177
+ description: "Add text based on the context.",
1178
+ searchTerms: ["ai"],
1179
+ icon: /* @__PURE__ */ jsxRuntime.jsx(ui.AutoAwesomeIcon, { size: 18 }),
1180
+ command: async ({
1181
+ editor,
1182
+ range,
1183
+ aiController
1184
+ }) => {
1185
+ if (!aiController)
1186
+ throw Error("No AiController");
1187
+ editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
1188
+ const { state: state2 } = editor;
1189
+ const {
1190
+ from,
1191
+ to
1192
+ } = state2.selection;
1193
+ const textBeforeCursor = state2.doc.textBetween(0, from, "\n");
1194
+ const textAfterCursor = state2.doc.textBetween(to, state2.doc.content.size, "\n");
1195
+ let buffer = "";
1196
+ const result = await aiController.autocomplete(textBeforeCursor, textAfterCursor, (delta) => {
1197
+ buffer += delta;
1198
+ if (delta.length !== 0) {
1199
+ editor.chain().focus().toggleLoadingDecoration(buffer).run();
1200
+ }
1201
+ });
1202
+ editor.chain().focus().insertContent(result, {
1203
+ applyInputRules: false,
1204
+ applyPasteRules: false,
1205
+ parseOptions: {
1206
+ preserveWhitespace: false
1207
+ }
1208
+ }).run();
1209
+ }
1210
+ };
1211
+ const suggestionItems = [
1212
+ {
1213
+ title: "Text",
1214
+ description: "Just start typing with plain text.",
1215
+ searchTerms: ["p", "paragraph"],
1216
+ icon: /* @__PURE__ */ jsxRuntime.jsx(ui.TextFieldsIcon, { size: 18 }),
1217
+ command: ({
1218
+ editor,
1219
+ range
1220
+ }) => {
1221
+ editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
1222
+ }
1223
+ },
1224
+ {
1225
+ title: "To-do List",
1226
+ description: "Track tasks with a to-do list.",
1227
+ searchTerms: ["todo", "task", "list", "check", "checkbox"],
1228
+ icon: /* @__PURE__ */ jsxRuntime.jsx(ui.CheckBoxIcon, { size: 18 }),
1229
+ command: ({
1230
+ editor,
1231
+ range
1232
+ }) => {
1233
+ editor.chain().focus().deleteRange(range).toggleTaskList().run();
1234
+ }
1235
+ },
1236
+ {
1237
+ title: "Heading 1",
1238
+ description: "Big section heading.",
1239
+ searchTerms: ["title", "big", "large"],
1240
+ icon: /* @__PURE__ */ jsxRuntime.jsx(ui.LooksOneIcon, { size: 18 }),
1241
+ command: ({
1242
+ editor,
1243
+ range
1244
+ }) => {
1245
+ editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
1246
+ }
1247
+ },
1248
+ {
1249
+ title: "Heading 2",
1250
+ description: "Medium section heading.",
1251
+ searchTerms: ["subtitle", "medium"],
1252
+ icon: /* @__PURE__ */ jsxRuntime.jsx(ui.LooksTwoIcon, { size: 18 }),
1253
+ command: ({
1254
+ editor,
1255
+ range
1256
+ }) => {
1257
+ editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run();
1258
+ }
1259
+ },
1260
+ {
1261
+ title: "Heading 3",
1262
+ description: "Small section heading.",
1263
+ searchTerms: ["subtitle", "small"],
1264
+ icon: /* @__PURE__ */ jsxRuntime.jsx(ui.Looks3Icon, { size: 18 }),
1265
+ command: ({
1266
+ editor,
1267
+ range
1268
+ }) => {
1269
+ editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run();
1270
+ }
1271
+ },
1272
+ {
1273
+ title: "Bullet List",
1274
+ description: "Create a simple bullet list.",
1275
+ searchTerms: ["unordered", "point"],
1276
+ icon: /* @__PURE__ */ jsxRuntime.jsx(ui.FormatListBulletedIcon, { size: 18 }),
1277
+ command: ({
1278
+ editor,
1279
+ range
1280
+ }) => {
1281
+ editor.chain().focus().deleteRange(range).toggleBulletList().run();
1282
+ }
1283
+ },
1284
+ {
1285
+ title: "Numbered List",
1286
+ description: "Create a list with numbering.",
1287
+ searchTerms: ["ordered"],
1288
+ icon: /* @__PURE__ */ jsxRuntime.jsx(ui.FormatListNumberedIcon, { size: 18 }),
1289
+ command: ({
1290
+ editor,
1291
+ range
1292
+ }) => {
1293
+ editor.chain().focus().deleteRange(range).toggleOrderedList().run();
1294
+ }
1295
+ },
1296
+ {
1297
+ title: "Quote",
1298
+ description: "Capture a quote.",
1299
+ searchTerms: ["blockquote"],
1300
+ icon: /* @__PURE__ */ jsxRuntime.jsx(ui.FormatQuoteIcon, { size: 18 }),
1301
+ command: ({
1302
+ editor,
1303
+ range
1304
+ }) => editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run()
1305
+ },
1306
+ {
1307
+ title: "Code",
1308
+ description: "Capture a code snippet.",
1309
+ searchTerms: ["codeblock"],
1310
+ icon: /* @__PURE__ */ jsxRuntime.jsx(ui.CodeIcon, { size: 18 }),
1311
+ command: ({
1312
+ editor,
1313
+ range
1314
+ }) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run()
1315
+ },
1316
+ {
1317
+ title: "Image",
1318
+ description: "Upload an image from your computer.",
1319
+ searchTerms: ["photo", "picture", "media", "upload", "file"],
1320
+ icon: /* @__PURE__ */ jsxRuntime.jsx(ui.ImageIcon, { size: 18 }),
1321
+ command: ({
1322
+ editor,
1323
+ range,
1324
+ upload
1325
+ }) => {
1326
+ editor.chain().focus().deleteRange(range).run();
1327
+ const input = document.createElement("input");
1328
+ input.type = "file";
1329
+ input.accept = "image/*";
1330
+ input.onchange = async () => {
1331
+ if (input.files?.length) {
1332
+ const file = input.files[0];
1333
+ if (!file) return;
1334
+ const pos = editor.view.state.selection.from;
1335
+ const fileList = input.files;
1336
+ const files = Array.from(fileList);
1337
+ const images = files.filter((file2) => /image/i.test(file2.type));
1338
+ if (images.length === 0) {
1339
+ console.log("No images found in uploaded files");
1340
+ return false;
1341
+ }
1342
+ const view2 = editor.view;
1343
+ images.forEach((image) => {
1344
+ const reader = new FileReader();
1345
+ reader.onload = async (readerEvent) => {
1346
+ await onFileRead(view2, readerEvent, pos, upload, image);
1347
+ };
1348
+ reader.readAsDataURL(image);
1349
+ });
1350
+ }
1351
+ return true;
1352
+ };
1353
+ input.click();
1354
+ }
1355
+ }
1356
+ ];
1357
+ function buildDecorationSet(highlight, doc) {
1358
+ const decorations = [];
1359
+ if (highlight) {
1360
+ decorations.push(
1361
+ view.Decoration.inline(highlight.from, highlight.to, {
1362
+ class: "dark:bg-slate-700 bg-slate-300"
1363
+ })
1364
+ );
1365
+ }
1366
+ const decorationSet = view.DecorationSet.create(doc, decorations);
1367
+ return decorationSet;
1368
+ }
1369
+ const HighlightDecorationExtension = (initialHighlight) => core.Extension.create({
1370
+ name: "highlightDecoration",
1371
+ addOptions() {
1372
+ return {
1373
+ pluginKey: new state.PluginKey("highlightDecoration"),
1374
+ highlight: initialHighlight
1375
+ };
1376
+ },
1377
+ addProseMirrorPlugins() {
1378
+ const pluginKey = this.options.pluginKey;
1379
+ return [
1380
+ new state.Plugin({
1381
+ key: pluginKey,
1382
+ state: {
1383
+ init: (_, { doc }) => {
1384
+ const highlight = this.options.highlight;
1385
+ const decorationSet = highlight && doc ? buildDecorationSet(highlight, doc) : view.DecorationSet.empty;
1386
+ return {
1387
+ decorationSet,
1388
+ highlight
1389
+ };
1390
+ },
1391
+ apply(transaction, oldState) {
1392
+ const action = transaction.getMeta(pluginKey);
1393
+ const highlight = action?.range;
1394
+ if (action?.type === "highlightDecoration") {
1395
+ const doc = transaction.doc;
1396
+ const { remove } = action;
1397
+ if (remove) {
1398
+ return {
1399
+ decorationSet: view.DecorationSet.empty
1400
+ };
1401
+ }
1402
+ const decorationSet = buildDecorationSet(highlight, doc);
1403
+ return {
1404
+ decorationSet,
1405
+ highlight
1406
+ };
1407
+ } else {
1408
+ return oldState;
1409
+ }
1410
+ }
1411
+ },
1412
+ props: {
1413
+ decorations(state2) {
1414
+ const autocompleteState = this.getState(state2);
1415
+ if (autocompleteState?.decorationSet) {
1416
+ return autocompleteState.decorationSet;
1417
+ } else {
1418
+ return view.DecorationSet.empty;
1419
+ }
1420
+ }
1421
+ }
1422
+ })
1423
+ ];
1424
+ },
1425
+ addCommands() {
1426
+ return {
1427
+ toggleAutocompleteHighlight: (range) => ({
1428
+ state: state2,
1429
+ dispatch
1430
+ }) => {
1431
+ const { selection } = state2;
1432
+ const pos = selection.from;
1433
+ if (!dispatch) return false;
1434
+ const pluginKey = this.options.pluginKey;
1435
+ const tr = state2.tr.setMeta(pluginKey, {
1436
+ pos,
1437
+ type: "highlightDecoration",
1438
+ remove: false,
1439
+ range
1440
+ });
1441
+ dispatch(tr);
1442
+ return true;
1443
+ },
1444
+ removeAutocompleteHighlight: () => ({
1445
+ state: state2,
1446
+ dispatch
1447
+ }) => {
1448
+ if (!dispatch) return false;
1449
+ const pluginKey = this.options.pluginKey;
1450
+ const tr = state2.tr.setMeta(pluginKey, {
1451
+ pos: 0,
1452
+ // We can pass any position as it will remove the entire decoration set
1453
+ type: "highlightDecoration",
1454
+ remove: true
1455
+ });
1456
+ dispatch(tr);
1457
+ return true;
1458
+ }
1459
+ };
1460
+ }
1461
+ });
1462
+ const CustomDocument = Document.extend({
1463
+ // content: 'heading block*',
1464
+ });
1465
+ const proseClasses = {
1466
+ "sm": "prose-sm",
1467
+ "base": "prose-base",
1468
+ "lg": "prose-lg"
1469
+ };
1470
+ const FireCMSEditor = ({
1471
+ content,
1472
+ onJsonContentChange,
1473
+ onHtmlContentChange,
1474
+ onMarkdownContentChange,
1475
+ version,
1476
+ textSize = "base",
1477
+ highlight,
1478
+ handleImageUpload,
1479
+ aiController,
1480
+ onDisabledAutocompleteClick
1481
+ }) => {
1482
+ const ref = React.useRef(null);
1483
+ const editorRef = React.useRef(null);
1484
+ const imagePlugin = createDropImagePlugin(handleImageUpload);
1485
+ const imageExtension = React.useMemo(() => createImageExtension(imagePlugin), []);
1486
+ const [openNode, setOpenNode] = React.useState(false);
1487
+ const [openLink, setOpenLink] = React.useState(false);
1488
+ ui.useInjectStyles("Editor", cssStyles);
1489
+ const deferredHighlight = React.useDeferredValue(highlight);
1490
+ React.useEffect(() => {
1491
+ if (version === void 0) return;
1492
+ if (version > -1 && editorRef.current) {
1493
+ editorRef.current?.commands.setContent(content ?? "");
1494
+ }
1495
+ }, [version]);
1496
+ React.useEffect(() => {
1497
+ if (version === void 0) return;
1498
+ if (editorRef.current && version > 0) {
1499
+ const chain = editorRef.current.chain();
1500
+ if (deferredHighlight) {
1501
+ chain.focus().toggleAutocompleteHighlight(deferredHighlight).run();
1502
+ } else {
1503
+ chain.focus().removeAutocompleteHighlight().run();
1504
+ }
1505
+ }
1506
+ }, [deferredHighlight?.from, deferredHighlight?.to]);
1507
+ const onEditorUpdate = (editor) => {
1508
+ editorRef.current = editor;
1509
+ if (onMarkdownContentChange) {
1510
+ const markdown = editorRef.current.storage.markdown.getMarkdown();
1511
+ onMarkdownContentChange?.(addLineBreakAfterImages(markdown));
1512
+ }
1513
+ if (onJsonContentChange) {
1514
+ const jsonContent = removeClassesFromJson(editor.getJSON());
1515
+ onJsonContentChange(jsonContent);
1516
+ }
1517
+ if (onHtmlContentChange) {
1518
+ onHtmlContentChange?.(editor.getHTML());
1519
+ }
1520
+ };
1521
+ const proseClass = proseClasses[textSize];
1522
+ const extensions = React.useMemo(() => [
1523
+ starterKit,
1524
+ CustomDocument,
1525
+ HighlightDecorationExtension(highlight),
1526
+ TextLoadingDecorationExtension,
1527
+ extensionUnderline.Underline,
1528
+ TextStyle,
1529
+ extensionColor.Color,
1530
+ BulletList,
1531
+ Highlight.configure({
1532
+ multicolor: true
1533
+ }),
1534
+ CustomKeymap,
1535
+ DragAndDrop,
1536
+ placeholder,
1537
+ tiptapLink,
1538
+ imageExtension,
1539
+ taskList,
1540
+ taskItem,
1541
+ markdownExtension,
1542
+ horizontalRule,
1543
+ SlashCommand.configure({
1544
+ HTMLAttributes: {
1545
+ class: "mention"
1546
+ },
1547
+ suggestion: suggestion(ref, {
1548
+ upload: handleImageUpload,
1549
+ aiController,
1550
+ onDisabledAutocompleteClick
1551
+ })
1552
+ })
1553
+ ], []);
1554
+ return /* @__PURE__ */ jsxRuntime.jsx(
1555
+ "div",
1556
+ {
1557
+ ref,
1558
+ className: "relative min-h-[300px] w-full",
1559
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1560
+ react.EditorProvider,
1561
+ {
1562
+ content: content ?? "",
1563
+ extensions,
1564
+ editorProps: {
1565
+ attributes: {
1566
+ class: ui.cls(proseClass, "prose-headings:font-title font-default focus:outline-none max-w-full p-12")
1567
+ }
1568
+ },
1569
+ onCreate: ({ editor }) => {
1570
+ editorRef.current = editor;
1571
+ },
1572
+ onUpdate: ({ editor }) => {
1573
+ onEditorUpdate(editor);
1574
+ },
1575
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1576
+ EditorBubble,
1577
+ {
1578
+ tippyOptions: {
1579
+ placement: "top"
1580
+ },
1581
+ className: ui.cls("flex w-fit max-w-[90vw] h-10 overflow-hidden rounded border bg-white dark:bg-gray-900 shadow", ui.defaultBorderMixin),
1582
+ children: [
1583
+ /* @__PURE__ */ jsxRuntime.jsx(NodeSelector, { portalContainer: ref.current, open: openNode, onOpenChange: setOpenNode }),
1584
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Separator, { orientation: "vertical" }),
1585
+ /* @__PURE__ */ jsxRuntime.jsx(LinkSelector, { open: openLink, onOpenChange: setOpenLink }),
1586
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Separator, { orientation: "vertical" }),
1587
+ /* @__PURE__ */ jsxRuntime.jsx(TextButtons, {})
1588
+ ]
1589
+ }
1590
+ )
1591
+ }
1592
+ )
1593
+ }
1594
+ );
1595
+ };
1596
+ function addLineBreakAfterImages(markdown) {
1597
+ const imageRegex = /!\[.*?\]\(.*?\)/g;
1598
+ return markdown.replace(imageRegex, (match) => `${match}`);
1599
+ }
1600
+ const cssStyles = `
1601
+ .ProseMirror {
1602
+ box-shadow: none !important;
1603
+ }
4
1604
  .ProseMirror .is-editor-empty:first-child::before {
5
1605
  content: attr(data-placeholder);
6
1606
  float: left;
@@ -23,6 +1623,7 @@
23
1623
  }
24
1624
 
25
1625
  .is-empty {
1626
+ cursor: text;
26
1627
  color: rgb(100 116 139); //500
27
1628
  }
28
1629
 
@@ -40,6 +1641,7 @@
40
1641
  &.ProseMirror-selectednode {
41
1642
  outline: 3px solid #5abbf7;
42
1643
  filter: brightness(90%);
1644
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000) !important;
43
1645
  }
44
1646
  }
45
1647
 
@@ -67,7 +1669,7 @@ ul[data-type="taskList"] li > label {
67
1669
  }
68
1670
 
69
1671
  &:active {
70
- background-color: rgb(71 85 105);;
1672
+ background-color: rgb(71 85 105);
71
1673
  }
72
1674
  }
73
1675
  }
@@ -134,7 +1736,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
134
1736
  }
135
1737
 
136
1738
  .ProseMirror:not(.dragging) .ProseMirror-selectednode {
137
- outline: none !important;
1739
+ // outline: none !important;
138
1740
  background-color: rgb(219 234 254); // blue 100
139
1741
  transition: background-color 0.2s;
140
1742
  box-shadow: none;
@@ -145,7 +1747,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
145
1747
  }
146
1748
 
147
1749
  .drag-handle {
148
- position: fixed;
1750
+ position: absolute;
149
1751
  opacity: 1;
150
1752
  transition: opacity ease-in 0.2s;
151
1753
  border-radius: 0.25rem;
@@ -156,7 +1758,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
156
1758
  background-position: center;
157
1759
  width: 1.2rem;
158
1760
  height: 1.5rem;
159
- z-index: 50;
1761
+ z-index: 100;
160
1762
  cursor: grab;
161
1763
 
162
1764
  &:hover {
@@ -189,5 +1791,8 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
189
1791
  background-color: rgb(51 65 85); // 700
190
1792
  }
191
1793
  }
192
- `;u.FireCMSEditor=Pe,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})});
1794
+ `;
1795
+ exports2.FireCMSEditor = FireCMSEditor;
1796
+ Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
1797
+ });
193
1798
  //# sourceMappingURL=index.umd.js.map