diff --git a/components/FolderOverview.tsx b/components/FolderOverview.tsx index 720f273..0da51f9 100644 --- a/components/FolderOverview.tsx +++ b/components/FolderOverview.tsx @@ -92,6 +92,7 @@ const DOC_TYPE_LABELS: Record = { source_code: 'Source code', pdf: 'PDFs', image: 'Images', + rich_text: 'Rich documents', }; const formatDocTypeLabel = (docType: DocType) => DOC_TYPE_LABELS[docType] ?? docType.replace(/_/g, ' '); diff --git a/components/PromptEditor.tsx b/components/PromptEditor.tsx index 0748381..baa0338 100644 --- a/components/PromptEditor.tsx +++ b/components/PromptEditor.tsx @@ -15,6 +15,8 @@ import LanguageDropdown from './LanguageDropdown'; import PythonExecutionPanel from './PythonExecutionPanel'; import ScriptExecutionPanel from './ScriptExecutionPanel'; import EmojiPickerOverlay from './EmojiPickerOverlay'; +import RichTextEditor, { type RichTextEditorHandle } from './RichTextEditor'; +import RichTextDiffView from './RichTextDiffView'; interface DocumentEditorProps { documentNode: DocumentOrFolder; @@ -149,6 +151,7 @@ const DocumentEditor: React.FC = ({ const [isCopied, setIsCopied] = useState(false); const [viewMode, setViewMode] = useState(resolveDefaultViewMode(documentNode.default_view_mode, documentNode.language_hint)); const [splitSize, setSplitSize] = useState(50); + const [editorEngine, setEditorEngine] = useState<'tiptap' | 'monaco'>(documentNode.doc_type === 'rich_text' ? 'tiptap' : 'monaco'); const isLocked = Boolean(documentNode.locked); const [isLocking, setIsLocking] = useState(false); const { addLog } = useLogger(); @@ -197,7 +200,10 @@ const DocumentEditor: React.FC = ({ const titleInputRef = useRef(null); const languageButtonRef = useRef(null); const isContentInitialized = useRef(false); - const editorRef = useRef(null); + const editorRef = useRef(null); + const codeEditorRef = useRef(null); + const richTextEditorRef = useRef(null); + const editorEnginePreferenceRef = useRef>(new Map()); const previewScrollRef = useRef(null); const isSyncing = useRef(false); const syncTimeout = useRef(null); @@ -239,6 +245,12 @@ const DocumentEditor: React.FC = ({ return () => window.removeEventListener('resize', handleWindowResize); }, [scriptPanelMinHeight]); + useEffect(() => { + const defaultEngine = documentNode.doc_type === 'rich_text' ? 'tiptap' : 'monaco'; + const storedPreference = editorEnginePreferenceRef.current.get(documentNode.id); + setEditorEngine(storedPreference ?? defaultEngine); + }, [documentNode.id, documentNode.doc_type]); + // Keep local editor state in sync with document updates without clobbering unsaved edits. useEffect(() => { const nextContent = documentNode.content ?? ''; @@ -277,6 +289,12 @@ const DocumentEditor: React.FC = ({ } }, [viewMode, isDiffMode]); + useEffect(() => { + if (isDiffMode) { + editorRef.current = null; + } + }, [isDiffMode]); + useEffect(() => { const normalizedHint = documentNode.language_hint?.toLowerCase(); if ((normalizedHint === 'pdf' || normalizedHint === 'application/pdf') && !documentNode.default_view_mode && viewMode === 'edit') { @@ -472,10 +490,44 @@ const DocumentEditor: React.FC = ({ onDelete(documentNode.id); }; + const isRichDocument = documentNode.doc_type === 'rich_text'; + const language = isRichDocument ? 'html' : (documentNode.language_hint || 'plaintext'); + const normalizedLanguage = language.toLowerCase(); + const supportsAiTools = ['markdown', 'plaintext', 'html'].includes(normalizedLanguage); + const canAddEmojiToTitle = documentNode.type === 'document'; + const supportsPreview = isRichDocument || PREVIEWABLE_LANGUAGES.has(normalizedLanguage); + const supportsFormatting = isRichDocument || ['javascript', 'typescript', 'json', 'html', 'css', 'xml', 'yaml'].includes(normalizedLanguage); + const isUsingTiptapEngine = isRichDocument && editorEngine === 'tiptap'; + const handleMonacoRef = useCallback((instance: CodeEditorHandle | null) => { + codeEditorRef.current = instance; + editorRef.current = instance; + }, []); + const handleRichTextRef = useCallback((instance: RichTextEditorHandle | null) => { + richTextEditorRef.current = instance; + editorRef.current = instance; + }, []); + const handleViewModeButton = useCallback((newMode: ViewMode) => { setViewMode(newMode); onViewModeChange(newMode); }, [onViewModeChange]); + + const handleEditorEngineChange = useCallback((engine: 'tiptap' | 'monaco') => { + if (!isRichDocument) { + return; + } + if (engine === editorEngine) { + return; + } + if (engine === 'monaco' && richTextEditorRef.current) { + const html = richTextEditorRef.current.getHTML(); + if (html !== content) { + setContent(html); + } + } + editorEnginePreferenceRef.current.set(documentNode.id, engine); + setEditorEngine(engine); + }, [content, documentNode.id, editorEngine, isRichDocument]); const handleFormatDocument = () => { if (isLocked) { @@ -483,7 +535,15 @@ const DocumentEditor: React.FC = ({ addLog('WARNING', `Format request blocked for locked document "${title}".`); return; } - editorRef.current?.format(); + const editor = editorRef.current; + if (!editor) { + return; + } + if ('getHTML' in editor) { + (editor as RichTextEditorHandle).format(); + } else { + (editor as CodeEditorHandle).format(); + } }; const handleRefine = useCallback(async () => { @@ -657,12 +717,6 @@ const DocumentEditor: React.FC = ({ setTimeout(() => setIsCopied(false), 2000); }, [content, addLog]); - const language = documentNode.language_hint || 'plaintext'; - const normalizedLanguage = language.toLowerCase(); - const supportsAiTools = ['markdown', 'plaintext'].includes(normalizedLanguage); - const canAddEmojiToTitle = documentNode.type === 'document'; - const supportsPreview = PREVIEWABLE_LANGUAGES.has(normalizedLanguage); - const supportsFormatting = ['javascript', 'typescript', 'json', 'html', 'css', 'xml', 'yaml'].includes(normalizedLanguage); const scriptBridgeAvailable = typeof window !== 'undefined' && (!!window.electronAPI || !!window.__DOCFORGE_SCRIPT_PREVIEW__); const isPythonDocument = typeof window !== 'undefined' && !!window.electronAPI && (normalizedLanguage === 'python'); @@ -820,8 +874,13 @@ const DocumentEditor: React.FC = ({ }, [onZoomTargetChange]); const renderContent = () => { - const editor = isDiffMode - ? ( + let editorElement: React.ReactNode; + + if (isDiffMode) { + if (isRichDocument && editorEngine === 'tiptap') { + editorElement = ; + } else { + editorElement = ( = ({ activeLineHighlightColorDark={settings.editorActiveLineHighlightColorDark} onFocusChange={handleEditorFocusChange} /> - ) - : ( - ); + } + } else if (isUsingTiptapEngine) { + editorElement = ( + + ); + } else { + editorElement = ( + + ); + } const preview = (
= ({ ); switch(viewMode) { - case 'edit': return editor; - case 'preview': return supportsPreview ? preview : editor; + case 'edit': return editorElement; + case 'preview': return supportsPreview ? preview : editorElement; case 'split-vertical': return (
-
{editor}
+
{editorElement}
-
{supportsPreview ? preview : editor}
+
{supportsPreview ? preview : editorElement}
); case 'split-horizontal': return (
-
{editor}
+
{editorElement}
-
{supportsPreview ? preview : editor}
+
{supportsPreview ? preview : editorElement}
); } @@ -976,6 +1049,24 @@ const DocumentEditor: React.FC = ({ handleViewModeButton('split-horizontal')} tooltip="Split Horizontal" size="xs" className={`rounded-md ${viewMode === 'split-horizontal' ? 'bg-secondary text-primary' : ''}`}>
)} + {isRichDocument && ( +
+ + +
+ )}
{supportsFormatting && ( diff --git a/components/RichTextDiffView.tsx b/components/RichTextDiffView.tsx new file mode 100644 index 0000000..a45b71b --- /dev/null +++ b/components/RichTextDiffView.tsx @@ -0,0 +1,41 @@ +import React, { useMemo } from 'react'; +import { diffWordsWithSpace } from 'diff'; + +const escapeHtml = (value: string) => + value + .replace(/&/g, '&') + .replace(//g, '>'); + +interface RichTextDiffViewProps { + baseline: string; + current: string; +} + +const RichTextDiffView: React.FC = ({ baseline, current }) => { + const diffMarkup = useMemo(() => { + const parts = diffWordsWithSpace(baseline, current); + return parts.map((part, index) => { + const className = part.added + ? 'bg-success/10 text-success' + : part.removed + ? 'bg-destructive-bg text-destructive-text' + : 'text-text-main'; + return ( + + ); + }); + }, [baseline, current]); + + return ( +
+ {diffMarkup} +
+ ); +}; + +export default RichTextDiffView; diff --git a/components/RichTextEditor.tsx b/components/RichTextEditor.tsx new file mode 100644 index 0000000..129e2d1 --- /dev/null +++ b/components/RichTextEditor.tsx @@ -0,0 +1,284 @@ +import React, { + forwardRef, + useEffect, + useImperativeHandle, + useMemo, + useRef, +} from 'react'; +import { EditorContent, useEditor } from '@tiptap/react'; +import StarterKit from '@tiptap/starter-kit'; +import Link from '@tiptap/extension-link'; +import Image from '@tiptap/extension-image'; +import type { Editor } from '@tiptap/react'; + +type ScrollInfo = { scrollTop: number; scrollHeight: number; clientHeight: number }; + +export interface RichTextEditorHandle { + format: () => void; + setScrollTop: (scrollTop: number) => void; + getScrollInfo: () => Promise; + getHTML: () => string; +} + +interface RichTextEditorProps { + content: string; + onChange: (nextContent: string) => void; + readOnly?: boolean; + onScroll?: (scrollInfo: ScrollInfo) => void; + onFocusChange?: (hasFocus: boolean) => void; +} + +const ToolbarButton: React.FC<{ + onClick: () => void; + label: string; + isActive?: boolean; + disabled?: boolean; + title?: string; +}> = ({ onClick, label, isActive = false, disabled = false, title }) => ( + +); + +const RichTextToolbar: React.FC<{ editor: Editor | null; readOnly: boolean }> = ({ editor, readOnly }) => { + if (!editor) return null; + + return ( +
+ editor.chain().focus().toggleBold().run()} + /> + editor.chain().focus().toggleItalic().run()} + /> + editor.chain().focus().toggleStrike().run()} + /> + editor.chain().focus().toggleCode().run()} + /> +
+ editor.chain().focus().setParagraph().run()} + /> + editor.chain().focus().toggleHeading({ level: 1 }).run()} + /> + editor.chain().focus().toggleHeading({ level: 2 }).run()} + /> + editor.chain().focus().toggleBulletList().run()} + /> + editor.chain().focus().toggleOrderedList().run()} + /> + editor.chain().focus().toggleBlockquote().run()} + /> +
+ editor.chain().focus().undo().run()} + /> + editor.chain().focus().redo().run()} + /> +
+ ); +}; + +const RichTextEditor = forwardRef(({ + content, + onChange, + readOnly = false, + onScroll, + onFocusChange, +}, ref) => { + const containerRef = useRef(null); + const lastSerializedRef = useRef(content); + + const extensions = useMemo(() => [ + StarterKit.configure({ + codeBlock: false, + }), + Link.configure({ + openOnClick: false, + }), + Image.configure({ inline: true }), + ], []); + + const editor = useEditor({ + extensions, + content: content || '

', + editable: !readOnly, + editorProps: { + attributes: { + class: 'prose prose-invert max-w-none min-h-full focus:outline-none', + 'data-placeholder': 'Start writing...' + }, + handleDOMEvents: { + focus: () => { + onFocusChange?.(true); + return false; + }, + blur: () => { + onFocusChange?.(false); + return false; + }, + }, + }, + onUpdate({ editor: tiptapEditor }) { + const html = tiptapEditor.getHTML(); + if (lastSerializedRef.current !== html) { + lastSerializedRef.current = html; + onChange(html); + } + }, + }); + + useEffect(() => { + if (!editor) { + return; + } + editor.setEditable(!readOnly); + }, [editor, readOnly]); + + useEffect(() => { + if (!editor) { + return; + } + const current = editor.getHTML(); + if (content && content !== current) { + editor.commands.setContent(content, false); + lastSerializedRef.current = content; + } + if (!content && current !== '

') { + editor.commands.setContent('

', false); + lastSerializedRef.current = '

'; + } + }, [editor, content]); + + useImperativeHandle(ref, () => ({ + format: () => { + if (!editor) return; + const html = editor.getHTML(); + editor.commands.setContent(html, false); + editor.chain().focus().run(); + lastSerializedRef.current = html; + onChange(html); + }, + setScrollTop: (scrollTop: number) => { + if (containerRef.current) { + containerRef.current.scrollTop = scrollTop; + } + }, + getScrollInfo: async () => { + const el = containerRef.current; + if (!el) { + return { scrollTop: 0, scrollHeight: 0, clientHeight: 0 }; + } + return { + scrollTop: el.scrollTop, + scrollHeight: el.scrollHeight, + clientHeight: el.clientHeight, + }; + }, + getHTML: () => { + if (editor) { + return editor.getHTML(); + } + return lastSerializedRef.current; + }, + }), [editor, onChange]); + + useEffect(() => { + return () => { + onFocusChange?.(false); + }; + }, [onFocusChange]); + + return ( +
+ +
{ + if (!onScroll) return; + const target = event.currentTarget; + onScroll({ + scrollTop: target.scrollTop, + scrollHeight: target.scrollHeight, + clientHeight: target.clientHeight, + }); + }} + > + +
+
+ ); +}); + +RichTextEditor.displayName = 'RichTextEditor'; + +export default RichTextEditor; diff --git a/electron/database.ts b/electron/database.ts index cd80a48..871ec86 100644 --- a/electron/database.ts +++ b/electron/database.ts @@ -921,7 +921,7 @@ export const databaseService = { } if (nodeType === 'document') { - const allowedDocTypes: DocType[] = ['prompt', 'source_code', 'pdf', 'image']; + const allowedDocTypes: DocType[] = ['prompt', 'source_code', 'pdf', 'image', 'rich_text']; const allowedViewModes: ViewMode[] = ['edit', 'preview', 'split-vertical', 'split-horizontal']; let docType = allowedDocTypes.includes(node.doc_type as DocType) diff --git a/hooks/usePrompts.ts b/hooks/usePrompts.ts index 1391c54..ce608dd 100644 --- a/hooks/usePrompts.ts +++ b/hooks/usePrompts.ts @@ -61,7 +61,7 @@ export const useDocuments = () => { const allNodesFlat = useMemo(() => flattenNodes(nodes), [nodes]); const items: DocumentOrFolder[] = useMemo(() => allNodesFlat.map(nodeToDocumentOrFolder), [allNodesFlat]); - const addDocument = useCallback(async ({ parentId, title = 'New Document', content = '', doc_type = 'prompt', language_hint = 'markdown' }: { parentId: string | null, title?: string, content?: string, doc_type?: DocType, language_hint?: string | null }) => { + const addDocument = useCallback(async ({ parentId, title = 'New Document', content = '', doc_type = 'rich_text', language_hint = 'html' }: { parentId: string | null, title?: string, content?: string, doc_type?: DocType, language_hint?: string | null }) => { const resolvedLanguage = mapExtensionToLanguageId(language_hint); const shouldPreviewByDefault = doc_type === 'pdf' || doc_type === 'image' || resolvedLanguage === 'pdf' || resolvedLanguage === 'image'; const defaultViewMode = shouldPreviewByDefault ? 'preview' : undefined; diff --git a/package-lock.json b/package-lock.json index 7f4445d..c97a885 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,13 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { + "@tiptap/extension-image": "^2.6.6", + "@tiptap/extension-link": "^2.6.6", + "@tiptap/react": "^2.6.6", + "@tiptap/starter-kit": "^2.6.6", "@uiw/react-color-compact": "^2.9.2", "better-sqlite3": "^11.1.2", + "diff": "^5.2.0", "electron-log": "^5.1.5", "electron-squirrel-startup": "^1.0.1", "electron-updater": "^6.2.1", @@ -1984,6 +1989,22 @@ "node": ">=14" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@remirror/core-constants": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", + "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", + "license": "MIT" + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.38", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz", @@ -2477,6 +2498,421 @@ } } }, + "node_modules/@tiptap/core": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.27.1.tgz", + "integrity": "sha512-nkerkl8syHj44ZzAB7oA2GPmmZINKBKCa79FuNvmGJrJ4qyZwlkDzszud23YteFZEytbc87kVd/fP76ROS6sLg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-blockquote": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.27.1.tgz", + "integrity": "sha512-QrUX3muElDrNjKM3nqCSAtm3H3pT33c6ON8kwRiQboOAjT/9D57Cs7XEVY7r6rMaJPeKztrRUrNVF9w/w/6B0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.27.1.tgz", + "integrity": "sha512-g4l4p892x/r7mhea8syp3fNYODxsDrimgouQ+q4DKXIgQmm5+uNhyuEPexP3I8TFNXqQ4DlMNFoM9yCqk97etQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.27.1.tgz", + "integrity": "sha512-ki1R27VsSvY2tT9Q2DIlcATwLOoEjf5DsN+5sExarQ8S/ZxT/tvIjRxB8Dx7lb2a818W5f/NER26YchGtmHfpg==", + "license": "MIT", + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-bullet-list": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.27.1.tgz", + "integrity": "sha512-5FmnfXkJ76wN4EbJNzBhAlmQxho8yEMIJLchTGmXdsD/n/tsyVVtewnQYaIOj/Z7naaGySTGDmjVtLgTuQ+Sxw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.27.1.tgz", + "integrity": "sha512-i65wUGJevzBTIIUBHBc1ggVa27bgemvGl/tY1/89fEuS/0Xmre+OQjw8rCtSLevoHSiYYLgLRlvjtUSUhE4kgg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.27.1.tgz", + "integrity": "sha512-wCI5VIOfSAdkenCWFvh4m8FFCJ51EOK+CUmOC/PWUjyo2Dgn8QC8HMi015q8XF7886T0KvYVVoqxmxJSUDAYNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.27.1.tgz", + "integrity": "sha512-NtJzJY7Q/6XWjpOm5OXKrnEaofrcc1XOTYlo/SaTwl8k2bZo918Vl0IDBWhPVDsUN7kx767uHwbtuQZ+9I82hA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-dropcursor": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.27.1.tgz", + "integrity": "sha512-3MBQRGHHZ0by3OT0CWbLKS7J3PH9PpobrXjmIR7kr0nde7+bHqxXiVNuuIf501oKU9rnEUSedipSHkLYGkmfsA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.27.1.tgz", + "integrity": "sha512-nUk/8DbiXO69l6FDwkWso94BTf52IBoWALo+YGWT6o+FO6cI9LbUGghEX2CdmQYXCvSvwvISF2jXeLQWNZvPZQ==", + "license": "MIT", + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-gapcursor": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.27.1.tgz", + "integrity": "sha512-A9e1jr+jGhDWzNSXtIO6PYVYhf5j/udjbZwMja+wCE/3KvZU9V3IrnGKz1xNW+2Q2BDOe1QO7j5uVL9ElR6nTA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-hard-break": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.27.1.tgz", + "integrity": "sha512-W4hHa4Io6QCTwpyTlN6UAvqMIQ7t56kIUByZhyY9EWrg/+JpbfpxE1kXFLPB4ZGgwBknFOw+e4bJ1j3oAbTJFw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-heading": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.27.1.tgz", + "integrity": "sha512-6xoC7igZlW1EmnQ5WVH9IL7P1nCQb3bBUaIDLvk7LbweEogcTUECI4Xg1vxMOVmj9tlDe1I4BsgfcKpB5KEsZw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-history": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.27.1.tgz", + "integrity": "sha512-K8PHC9gegSAt0wzSlsd4aUpoEyIJYOmVVeyniHr1P1mIblW1KYEDbRGbDlrLALTyUEfMcBhdIm8zrB9X2Nihvg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.27.1.tgz", + "integrity": "sha512-WxXWGEEsqDmGIF2o9av+3r9Qje4CKrqrpeQY6aRO5bxvWX9AabQCfasepayBok6uwtvNzh3Xpsn9zbbSk09dNA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-image": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.27.1.tgz", + "integrity": "sha512-wu3vMKDYWJwKS6Hrw5PPCKBO2RxyHNeFLiA/uDErEV7axzNpievK/U9DyaDXmtK3K/h1XzJAJz19X+2d/pY68w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.27.1.tgz", + "integrity": "sha512-rcm0GyniWW0UhcNI9+1eIK64GqWQLyIIrWGINslvqSUoBc+WkfocLvv4CMpRkzKlfsAxwVIBuH2eLxHKDtAREA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-link": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.27.1.tgz", + "integrity": "sha512-cCwWPZsnVh9MXnGOqSIRXPPuUixRDK8eMN2TvqwbxUBb1TU7b/HtNvfMU4tAOqAuMRJ0aJkFuf3eB0Gi8LVb1g==", + "license": "MIT", + "dependencies": { + "linkifyjs": "^4.3.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-list-item": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.27.1.tgz", + "integrity": "sha512-dtsxvtzxfwOJP6dKGf0vb2MJAoDF2NxoiWzpq0XTvo7NGGYUHfuHjX07Zp0dYqb4seaDXjwsi5BIQUOp3+WMFQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-ordered-list": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.27.1.tgz", + "integrity": "sha512-U1/sWxc2TciozQsZjH35temyidYUjvroHj3PUPzPyh19w2fwKh1NSbFybWuoYs6jS3XnMSwnM2vF52tOwvfEmA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.27.1.tgz", + "integrity": "sha512-R3QdrHcUdFAsdsn2UAIvhY0yWyHjqGyP/Rv8RRdN0OyFiTKtwTPqreKMHKJOflgX4sMJl/OpHTpNG1Kaf7Lo2A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.27.1.tgz", + "integrity": "sha512-S9I//K8KPgfFTC5I5lorClzXk0g4lrAv9y5qHzHO5EOWt7AFl0YTg2oN8NKSIBK4bHRnPIrjJJKv+dDFnUp5jQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.27.1.tgz", + "integrity": "sha512-a4GCT+GZ9tUwl82F4CEum9/+WsuW0/De9Be/NqrMmi7eNfAwbUTbLCTFU0gEvv25WMHCoUzaeNk/qGmzeVPJ1Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-text-style": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.27.1.tgz", + "integrity": "sha512-NagQ9qLk0Ril83gfrk+C65SvTqPjL3WVnLF2arsEVnCrxcx3uDOvdJW67f/K5HEwEHsoqJ4Zq9Irco/koXrOXA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/pm": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.27.1.tgz", + "integrity": "sha512-ijKo3+kIjALthYsnBmkRXAuw2Tswd9gd7BUR5OMfIcjGp8v576vKxOxrRfuYiUM78GPt//P0sVc1WV82H5N0PQ==", + "license": "MIT", + "dependencies": { + "prosemirror-changeset": "^2.3.0", + "prosemirror-collab": "^1.3.1", + "prosemirror-commands": "^1.6.2", + "prosemirror-dropcursor": "^1.8.1", + "prosemirror-gapcursor": "^1.3.2", + "prosemirror-history": "^1.4.1", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-markdown": "^1.13.1", + "prosemirror-menu": "^1.2.4", + "prosemirror-model": "^1.23.0", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.4.1", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.6.4", + "prosemirror-trailing-node": "^3.0.0", + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.37.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/react": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.27.1.tgz", + "integrity": "sha512-leJximSjYJuhLJQv9azOP9R7w6zuxVgKOHYT4w83Gte7GhWMpNL6xRWzld280vyq/YW/cSYjPb/8ESEOgKNBdQ==", + "license": "MIT", + "dependencies": { + "@tiptap/extension-bubble-menu": "^2.27.1", + "@tiptap/extension-floating-menu": "^2.27.1", + "@types/use-sync-external-store": "^0.0.6", + "fast-deep-equal": "^3", + "use-sync-external-store": "^1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tiptap/starter-kit": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.27.1.tgz", + "integrity": "sha512-uQQlP0Nmn9eq19qm8YoOeloEfmcGbPpB1cujq54Q6nPgxaBozR7rE7tXbFTinxRW2+Hr7XyNWhpjB7DMNkdU2Q==", + "license": "MIT", + "dependencies": { + "@tiptap/core": "^2.27.1", + "@tiptap/extension-blockquote": "^2.27.1", + "@tiptap/extension-bold": "^2.27.1", + "@tiptap/extension-bullet-list": "^2.27.1", + "@tiptap/extension-code": "^2.27.1", + "@tiptap/extension-code-block": "^2.27.1", + "@tiptap/extension-document": "^2.27.1", + "@tiptap/extension-dropcursor": "^2.27.1", + "@tiptap/extension-gapcursor": "^2.27.1", + "@tiptap/extension-hard-break": "^2.27.1", + "@tiptap/extension-heading": "^2.27.1", + "@tiptap/extension-history": "^2.27.1", + "@tiptap/extension-horizontal-rule": "^2.27.1", + "@tiptap/extension-italic": "^2.27.1", + "@tiptap/extension-list-item": "^2.27.1", + "@tiptap/extension-ordered-list": "^2.27.1", + "@tiptap/extension-paragraph": "^2.27.1", + "@tiptap/extension-strike": "^2.27.1", + "@tiptap/extension-text": "^2.27.1", + "@tiptap/extension-text-style": "^2.27.1", + "@tiptap/pm": "^2.27.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -2665,6 +3101,22 @@ "@types/node": "*" } }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -2675,6 +3127,12 @@ "@types/unist": "^2" } }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "license": "MIT" + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -2749,6 +3207,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -4293,6 +4757,12 @@ "node": ">= 10" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -5110,7 +5580,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -5813,9 +6282,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=10" }, @@ -5913,7 +6380,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -7653,6 +8119,21 @@ "dev": true, "license": "MIT" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/linkifyjs": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz", + "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", + "license": "MIT" + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -7794,6 +8275,35 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", @@ -13291,6 +13801,12 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -15204,6 +15720,12 @@ "opener": "bin/opener-bin.js" } }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", + "license": "MIT" + }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -15677,6 +16199,201 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/prosemirror-changeset": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.3.1.tgz", + "integrity": "sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==", + "license": "MIT", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-collab": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", + "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", + "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.0.tgz", + "integrity": "sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz", + "integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-markdown": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.2.tgz", + "integrity": "sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==", + "license": "MIT", + "dependencies": { + "@types/markdown-it": "^14.0.0", + "markdown-it": "^14.0.0", + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-menu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.5.tgz", + "integrity": "sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==", + "license": "MIT", + "dependencies": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.4", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", + "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", + "license": "MIT", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz", + "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.1.tgz", + "integrity": "sha512-DAgDoUYHCcc6tOGpLVPSU1k84kCUWTWnfWX3UDy2Delv4ryH0KqTD6RBI6k4yi9j9I8gl3j8MkPpRD/vWPZbug==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.2.2", + "prosemirror-model": "^1.25.0", + "prosemirror-state": "^1.4.3", + "prosemirror-transform": "^1.10.3", + "prosemirror-view": "^1.39.1" + } + }, + "node_modules/prosemirror-trailing-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", + "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", + "license": "MIT", + "dependencies": { + "@remirror/core-constants": "3.0.0", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.22.1", + "prosemirror-state": "^1.4.2", + "prosemirror-view": "^1.33.8" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.5.tgz", + "integrity": "sha512-RPDQCxIDhIBb1o36xxwsaeAvivO8VLJcgBtzmOwQ64bMtsVFh5SSuJ6dWSxO1UsHTiTXPCgQm3PDJt7p6IOLbw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.41.3", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.3.tgz", + "integrity": "sha512-SqMiYMUQNNBP9kfPhLO8WXEk/fon47vc52FQsUiJzTBuyjKgEcoAwMyF04eQ4WZ2ArMn7+ReypYL60aKngbACQ==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -15697,6 +16414,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -16822,6 +17548,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", + "license": "MIT" + }, "node_modules/rrweb-cssom": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", @@ -17908,6 +18640,15 @@ "node": ">=14.0.0" } }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "license": "MIT", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, "node_modules/tldts": { "version": "7.0.17", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz", @@ -18084,6 +18825,12 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -18333,6 +19080,15 @@ "dev": true, "license": "MIT" }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/utf8-byte-length": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", @@ -19139,6 +19895,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index 616a38f..ce57b18 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,13 @@ "url": "https://github.com/beNative/docforge.git" }, "dependencies": { + "@tiptap/extension-image": "^2.6.6", + "@tiptap/extension-link": "^2.6.6", + "@tiptap/react": "^2.6.6", + "@tiptap/starter-kit": "^2.6.6", "@uiw/react-color-compact": "^2.9.2", "better-sqlite3": "^11.1.2", + "diff": "^5.2.0", "electron-log": "^5.1.5", "electron-squirrel-startup": "^1.0.1", "electron-updater": "^6.2.1", diff --git a/services/classificationService.ts b/services/classificationService.ts index 9b5c081..5825dc2 100644 --- a/services/classificationService.ts +++ b/services/classificationService.ts @@ -205,6 +205,9 @@ export const classifyDocumentContent = (options: ClassificationOptions): Classif if (langFromExtension === 'image') { return classifyWith('image', 'image', 'preview', 1, 'Extension indicates image'); } + if (langFromExtension === 'html') { + return classifyWith('html', 'rich_text', null, 0.7, 'Extension indicates HTML document'); + } if (langFromExtension === 'markdown') { return classifyWith('markdown', 'prompt', null, 0.65, 'Extension indicates Markdown'); } @@ -233,7 +236,7 @@ export const classifyDocumentContent = (options: ClassificationOptions): Classif } if (looksLikeHtml(trimmed)) { - return classifyWith('html', 'source_code', null, 0.75, 'HTML tag heuristics matched'); + return classifyWith('html', 'rich_text', null, 0.75, 'HTML tag heuristics matched'); } if (looksLikeYaml(trimmed)) { diff --git a/services/documentExportService.ts b/services/documentExportService.ts index 117af02..48fd917 100644 --- a/services/documentExportService.ts +++ b/services/documentExportService.ts @@ -21,6 +21,7 @@ const DEFAULT_DOC_TYPE_EXTENSION: Record = { source_code: 'txt', pdf: 'pdf', image: 'png', + rich_text: 'html', }; const DOC_TYPE_FILTER_LABELS: Partial> = { @@ -28,6 +29,7 @@ const DOC_TYPE_FILTER_LABELS: Partial> = { source_code: 'Code Files', pdf: 'PDF Documents', image: 'Image Files', + rich_text: 'Rich Documents', }; const LANGUAGE_EXTENSION_MAP: Record = { diff --git a/services/repository.ts b/services/repository.ts index ba6dc91..54054ea 100644 --- a/services/repository.ts +++ b/services/repository.ts @@ -70,15 +70,15 @@ const createSampleBrowserState = (): BrowserState => { const versionId = 1; const shellVersionId = 2; const powershellVersionId = 3; - const sampleContent = '# Welcome to DocForge\n\nThis is a static dataset provided for browser preview mode.'; + const sampleContent = '

Welcome to DocForge

This is a static dataset provided for browser preview mode.

'; const shellContent = '#!/bin/bash\n\necho "DocForge shell quickstart"\nls -la'; const powershellContent = 'Write-Host "DocForge PowerShell quickstart"\nGet-ChildItem'; const document: Document = { document_id: documentId, node_id: documentNodeId, - doc_type: 'prompt', - language_hint: 'markdown', + doc_type: 'rich_text', + language_hint: 'html', default_view_mode: 'split-vertical', language_source: 'user', doc_type_source: 'user', @@ -1317,7 +1317,7 @@ export const repository = { const { collection, parentId, insertIndex } = resolveTarget(); - const allowedDocTypes: DocType[] = ['prompt', 'source_code', 'pdf', 'image']; + const allowedDocTypes: DocType[] = ['prompt', 'source_code', 'pdf', 'image', 'rich_text']; const allowedViewModes: ViewMode[] = ['edit', 'preview', 'split-vertical', 'split-horizontal']; const createdIds: string[] = []; diff --git a/styles/tailwind.css b/styles/tailwind.css index 9e02006..250cffb 100644 --- a/styles/tailwind.css +++ b/styles/tailwind.css @@ -20,3 +20,50 @@ background: transparent !important; border: none !important; } + +@layer base { + .rich-text-editor .ProseMirror { + min-height: 100%; + outline: none; + caret-color: rgb(var(--color-text-main)); + } + + .rich-text-editor .ProseMirror:focus { + outline: none; + } + + .rich-text-editor .ProseMirror p.is-editor-empty:first-child::before { + color: rgb(var(--color-text-secondary)); + content: attr(data-placeholder); + float: left; + height: 0; + pointer-events: none; + } + + .rich-text-editor .ProseMirror pre { + background-color: rgb(var(--color-secondary)); + border-radius: 0.5rem; + padding: 0.75rem; + font-family: ui-monospace, SFMono-Regular, SFMono, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + } + + .rich-text-editor .ProseMirror code { + background-color: rgb(var(--color-secondary)); + border-radius: 0.25rem; + padding: 0 0.25rem; + font-family: ui-monospace, SFMono-Regular, SFMono, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + } + + .rich-text-editor .ProseMirror ul, + .rich-text-editor .ProseMirror ol { + padding-left: 1.5rem; + } + + .rich-text-editor .ProseMirror blockquote { + border-left: 3px solid rgb(var(--color-border)); + margin-left: 0; + padding-left: 1rem; + color: rgb(var(--color-text-secondary)); + font-style: italic; + } +} diff --git a/types.ts b/types.ts index 7d787c0..ab734c1 100644 --- a/types.ts +++ b/types.ts @@ -144,7 +144,7 @@ export interface UpdateErrorPayload { } export type NodeType = 'folder' | 'document'; -export type DocType = 'prompt' | 'source_code' | 'pdf' | 'image'; +export type DocType = 'prompt' | 'source_code' | 'pdf' | 'image' | 'rich_text'; export type ClassificationSource = 'auto' | 'user' | 'imported' | 'unknown'; export type SaveFilePayload =