diff --git a/surfsense_backend/alembic/versions/101_add_source_markdown_to_documents.py b/surfsense_backend/alembic/versions/101_add_source_markdown_to_documents.py index 538cae2a1..4a9f3d8f7 100644 --- a/surfsense_backend/alembic/versions/101_add_source_markdown_to_documents.py +++ b/surfsense_backend/alembic/versions/101_add_source_markdown_to_documents.py @@ -72,7 +72,9 @@ def _populate_source_markdown(conn, batch_size: int = 500) -> None: print("No documents with blocknote_document need migration") return - print(f" Migrating {total} documents (with blocknote_document) to source_markdown...") + print( + f" Migrating {total} documents (with blocknote_document) to source_markdown..." + ) migrated = 0 failed = 0 diff --git a/surfsense_backend/app/agents/new_chat/tools/notion/create_page.py b/surfsense_backend/app/agents/new_chat/tools/notion/create_page.py index 3d4b84ee0..ce00025a4 100644 --- a/surfsense_backend/app/agents/new_chat/tools/notion/create_page.py +++ b/surfsense_backend/app/agents/new_chat/tools/notion/create_page.py @@ -107,8 +107,12 @@ def create_create_notion_page_tool( } ) - decisions_raw = approval.get("decisions", []) if isinstance(approval, dict) else [] - decisions = decisions_raw if isinstance(decisions_raw, list) else [decisions_raw] + decisions_raw = ( + approval.get("decisions", []) if isinstance(approval, dict) else [] + ) + decisions = ( + decisions_raw if isinstance(decisions_raw, list) else [decisions_raw] + ) decisions = [d for d in decisions if isinstance(d, dict)] if not decisions: logger.warning("No approval decision received") diff --git a/surfsense_backend/app/agents/new_chat/tools/notion/delete_page.py b/surfsense_backend/app/agents/new_chat/tools/notion/delete_page.py index c07de407e..065a4f9d4 100644 --- a/surfsense_backend/app/agents/new_chat/tools/notion/delete_page.py +++ b/surfsense_backend/app/agents/new_chat/tools/notion/delete_page.py @@ -119,8 +119,12 @@ def create_delete_notion_page_tool( } ) - decisions_raw = approval.get("decisions", []) if isinstance(approval, dict) else [] - decisions = decisions_raw if isinstance(decisions_raw, list) else [decisions_raw] + decisions_raw = ( + approval.get("decisions", []) if isinstance(approval, dict) else [] + ) + decisions = ( + decisions_raw if isinstance(decisions_raw, list) else [decisions_raw] + ) decisions = [d for d in decisions if isinstance(d, dict)] if not decisions: logger.warning("No approval decision received") diff --git a/surfsense_backend/app/agents/new_chat/tools/notion/update_page.py b/surfsense_backend/app/agents/new_chat/tools/notion/update_page.py index 17a9aa6db..7a58f3aad 100644 --- a/surfsense_backend/app/agents/new_chat/tools/notion/update_page.py +++ b/surfsense_backend/app/agents/new_chat/tools/notion/update_page.py @@ -128,8 +128,12 @@ def create_update_notion_page_tool( } ) - decisions_raw = approval.get("decisions", []) if isinstance(approval, dict) else [] - decisions = decisions_raw if isinstance(decisions_raw, list) else [decisions_raw] + decisions_raw = ( + approval.get("decisions", []) if isinstance(approval, dict) else [] + ) + decisions = ( + decisions_raw if isinstance(decisions_raw, list) else [decisions_raw] + ) decisions = [d for d in decisions if isinstance(d, dict)] if not decisions: logger.warning("No approval decision received") diff --git a/surfsense_backend/app/routes/editor_routes.py b/surfsense_backend/app/routes/editor_routes.py index 26a5ca12e..84846ef38 100644 --- a/surfsense_backend/app/routes/editor_routes.py +++ b/surfsense_backend/app/routes/editor_routes.py @@ -126,9 +126,7 @@ async def get_editor_content( "title": document.title, "document_type": document.document_type.value, "source_markdown": markdown_content, - "updated_at": document.updated_at.isoformat() - if document.updated_at - else None, + "updated_at": document.updated_at.isoformat() if document.updated_at else None, } @@ -172,14 +170,10 @@ async def save_document( source_markdown = data.get("source_markdown") if source_markdown is None: - raise HTTPException( - status_code=400, detail="source_markdown is required" - ) + raise HTTPException(status_code=400, detail="source_markdown is required") if not isinstance(source_markdown, str): - raise HTTPException( - status_code=400, detail="source_markdown must be a string" - ) + raise HTTPException(status_code=400, detail="source_markdown must be a string") # For NOTE type, extract title from first heading line if present if document.document_type == DocumentType.NOTE: diff --git a/surfsense_backend/app/utils/blocknote_to_markdown.py b/surfsense_backend/app/utils/blocknote_to_markdown.py index 467c0e7f6..a01d8d944 100644 --- a/surfsense_backend/app/utils/blocknote_to_markdown.py +++ b/surfsense_backend/app/utils/blocknote_to_markdown.py @@ -22,6 +22,7 @@ logger = logging.getLogger(__name__) # Inline content → markdown text # --------------------------------------------------------------------------- + def _render_inline_content(content: list[dict[str, Any]] | None) -> str: """Convert BlockNote inline content array to a markdown string.""" if not content: @@ -215,6 +216,7 @@ def _render_block(block: dict[str, Any], indent: int = 0) -> list[str]: # Public API # --------------------------------------------------------------------------- + def blocknote_to_markdown( blocks: list[dict[str, Any]] | dict[str, Any] | None, ) -> str | None: @@ -248,7 +250,9 @@ def blocknote_to_markdown( blocks = [blocks] if not isinstance(blocks, list): - logger.warning(f"blocknote_to_markdown received unexpected type: {type(blocks)}") + logger.warning( + f"blocknote_to_markdown received unexpected type: {type(blocks)}" + ) return None all_lines: list[str] = [] @@ -272,10 +276,10 @@ def blocknote_to_markdown( # Add a blank line between blocks (standard markdown spacing) # Exception: consecutive list items of the same type don't get extra blank lines if all_lines and block_lines: - same_list = ( - (block_type == prev_type and block_type in ( - "bulletListItem", "numberedListItem", "checkListItem" - )) + same_list = block_type == prev_type and block_type in ( + "bulletListItem", + "numberedListItem", + "checkListItem", ) if not same_list: all_lines.append("") @@ -285,4 +289,3 @@ def blocknote_to_markdown( result = "\n".join(all_lines).strip() return result if result else None - diff --git a/surfsense_web/app/dashboard/[search_space_id]/editor/[documentId]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/editor/[documentId]/page.tsx index e66b3e8f6..98564a8ba 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/editor/[documentId]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/editor/[documentId]/page.tsx @@ -27,7 +27,14 @@ import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth- // Dynamically import PlateEditor (uses 'use client' internally) const PlateEditor = dynamic( () => import("@/components/editor/plate-editor").then((mod) => ({ default: mod.PlateEditor })), - { ssr: false, loading: () =>
} + { + ssr: false, + loading: () => ( +
+ +
+ ), + } ); interface EditorContent { @@ -182,15 +189,12 @@ export default function EditorPage() { }, [isNote, document?.title, document?.source_markdown, hasUnsavedChanges]); // Handle markdown changes from the Plate editor - const handleMarkdownChange = useCallback( - (md: string) => { - markdownRef.current = md; - if (initialLoadDone.current) { - setHasUnsavedChanges(true); - } - }, - [] - ); + const handleMarkdownChange = useCallback((md: string) => { + markdownRef.current = md; + if (initialLoadDone.current) { + setHasUnsavedChanges(true); + } + }, []); // Save handler const handleSave = useCallback(async () => { @@ -438,10 +442,11 @@ export default function EditorPage() { Cancel - - Save - - + Save + Leave without saving diff --git a/surfsense_web/components.json b/surfsense_web/components.json index 35ebd5cd0..6086c498b 100644 --- a/surfsense_web/components.json +++ b/surfsense_web/components.json @@ -1,24 +1,24 @@ { - "$schema": "https://ui.shadcn.com/schema.json", - "style": "new-york", - "rsc": true, - "tsx": true, - "tailwind": { - "config": "", - "css": "app/globals.css", - "baseColor": "neutral", - "cssVariables": true, - "prefix": "" - }, - "iconLibrary": "lucide", - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" - }, - "registries": { - "@plate": "https://platejs.org/r/{name}.json" - } + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": { + "@plate": "https://platejs.org/r/{name}.json" + } } diff --git a/surfsense_web/components/editor/editor-save-context.tsx b/surfsense_web/components/editor/editor-save-context.tsx index b72eec0ae..d53a4adce 100644 --- a/surfsense_web/components/editor/editor-save-context.tsx +++ b/surfsense_web/components/editor/editor-save-context.tsx @@ -1,25 +1,24 @@ -'use client'; +"use client"; -import { createContext, useContext } from 'react'; +import { createContext, useContext } from "react"; interface EditorSaveContextValue { - /** Callback to save the current editor content */ - onSave?: () => void; - /** Whether there are unsaved changes */ - hasUnsavedChanges: boolean; - /** Whether a save operation is in progress */ - isSaving: boolean; - /** Whether the user can toggle between editing and viewing modes */ - canToggleMode: boolean; + /** Callback to save the current editor content */ + onSave?: () => void; + /** Whether there are unsaved changes */ + hasUnsavedChanges: boolean; + /** Whether a save operation is in progress */ + isSaving: boolean; + /** Whether the user can toggle between editing and viewing modes */ + canToggleMode: boolean; } export const EditorSaveContext = createContext({ - hasUnsavedChanges: false, - isSaving: false, - canToggleMode: false, + hasUnsavedChanges: false, + isSaving: false, + canToggleMode: false, }); export function useEditorSave() { - return useContext(EditorSaveContext); + return useContext(EditorSaveContext); } - diff --git a/surfsense_web/components/editor/plate-editor.tsx b/surfsense_web/components/editor/plate-editor.tsx index e559f85af..29eeb02f6 100644 --- a/surfsense_web/components/editor/plate-editor.tsx +++ b/surfsense_web/components/editor/plate-editor.tsx @@ -1,152 +1,150 @@ -'use client'; +"use client"; -import { useEffect, useRef } from 'react'; -import { MarkdownPlugin, remarkMdx } from '@platejs/markdown'; -import { Plate, usePlateEditor } from 'platejs/react'; -import remarkGfm from 'remark-gfm'; -import remarkMath from 'remark-math'; +import { useEffect, useRef } from "react"; +import { MarkdownPlugin, remarkMdx } from "@platejs/markdown"; +import { Plate, usePlateEditor } from "platejs/react"; +import remarkGfm from "remark-gfm"; +import remarkMath from "remark-math"; -import { AutoformatKit } from '@/components/editor/plugins/autoformat-kit'; -import { BasicNodesKit } from '@/components/editor/plugins/basic-nodes-kit'; -import { CalloutKit } from '@/components/editor/plugins/callout-kit'; -import { CodeBlockKit } from '@/components/editor/plugins/code-block-kit'; -import { DndKit } from '@/components/editor/plugins/dnd-kit'; -import { FixedToolbarKit } from '@/components/editor/plugins/fixed-toolbar-kit'; -import { FloatingToolbarKit } from '@/components/editor/plugins/floating-toolbar-kit'; -import { LinkKit } from '@/components/editor/plugins/link-kit'; -import { ListKit } from '@/components/editor/plugins/list-kit'; -import { MathKit } from '@/components/editor/plugins/math-kit'; -import { SelectionKit } from '@/components/editor/plugins/selection-kit'; -import { SlashCommandKit } from '@/components/editor/plugins/slash-command-kit'; -import { TableKit } from '@/components/editor/plugins/table-kit'; -import { ToggleKit } from '@/components/editor/plugins/toggle-kit'; -import { Editor, EditorContainer } from '@/components/ui/editor'; -import { escapeMdxExpressions } from '@/components/editor/utils/escape-mdx'; -import { EditorSaveContext } from '@/components/editor/editor-save-context'; +import { AutoformatKit } from "@/components/editor/plugins/autoformat-kit"; +import { BasicNodesKit } from "@/components/editor/plugins/basic-nodes-kit"; +import { CalloutKit } from "@/components/editor/plugins/callout-kit"; +import { CodeBlockKit } from "@/components/editor/plugins/code-block-kit"; +import { DndKit } from "@/components/editor/plugins/dnd-kit"; +import { FixedToolbarKit } from "@/components/editor/plugins/fixed-toolbar-kit"; +import { FloatingToolbarKit } from "@/components/editor/plugins/floating-toolbar-kit"; +import { LinkKit } from "@/components/editor/plugins/link-kit"; +import { ListKit } from "@/components/editor/plugins/list-kit"; +import { MathKit } from "@/components/editor/plugins/math-kit"; +import { SelectionKit } from "@/components/editor/plugins/selection-kit"; +import { SlashCommandKit } from "@/components/editor/plugins/slash-command-kit"; +import { TableKit } from "@/components/editor/plugins/table-kit"; +import { ToggleKit } from "@/components/editor/plugins/toggle-kit"; +import { Editor, EditorContainer } from "@/components/ui/editor"; +import { escapeMdxExpressions } from "@/components/editor/utils/escape-mdx"; +import { EditorSaveContext } from "@/components/editor/editor-save-context"; interface PlateEditorProps { - /** Markdown string to load as initial content */ - markdown?: string; - /** Called when the editor content changes, with serialized markdown */ - onMarkdownChange?: (markdown: string) => void; - /** - * Force permanent read-only mode (e.g. public/shared view). - * When true, the editor cannot be toggled to editing mode. - * When false (default), the editor starts in viewing mode but - * the user can switch to editing via the mode toolbar button. - */ - readOnly?: boolean; - /** Placeholder text */ - placeholder?: string; - /** Editor container variant */ - variant?: 'default' | 'demo' | 'comment' | 'select'; - /** Editor text variant */ - editorVariant?: 'default' | 'demo' | 'fullWidth' | 'none'; - /** Additional className for the container */ - className?: string; - /** Save callback. When provided, a save button appears in the toolbar on unsaved changes. */ - onSave?: () => void; - /** Whether there are unsaved changes */ - hasUnsavedChanges?: boolean; - /** Whether a save is in progress */ - isSaving?: boolean; - /** Start the editor in editing mode instead of viewing mode. Ignored when readOnly is true. */ - defaultEditing?: boolean; + /** Markdown string to load as initial content */ + markdown?: string; + /** Called when the editor content changes, with serialized markdown */ + onMarkdownChange?: (markdown: string) => void; + /** + * Force permanent read-only mode (e.g. public/shared view). + * When true, the editor cannot be toggled to editing mode. + * When false (default), the editor starts in viewing mode but + * the user can switch to editing via the mode toolbar button. + */ + readOnly?: boolean; + /** Placeholder text */ + placeholder?: string; + /** Editor container variant */ + variant?: "default" | "demo" | "comment" | "select"; + /** Editor text variant */ + editorVariant?: "default" | "demo" | "fullWidth" | "none"; + /** Additional className for the container */ + className?: string; + /** Save callback. When provided, a save button appears in the toolbar on unsaved changes. */ + onSave?: () => void; + /** Whether there are unsaved changes */ + hasUnsavedChanges?: boolean; + /** Whether a save is in progress */ + isSaving?: boolean; + /** Start the editor in editing mode instead of viewing mode. Ignored when readOnly is true. */ + defaultEditing?: boolean; } export function PlateEditor({ - markdown, - onMarkdownChange, - readOnly = false, - placeholder = 'Type...', - variant = 'default', - editorVariant = 'default', - className, - onSave, - hasUnsavedChanges = false, - isSaving = false, - defaultEditing = false, + markdown, + onMarkdownChange, + readOnly = false, + placeholder = "Type...", + variant = "default", + editorVariant = "default", + className, + onSave, + hasUnsavedChanges = false, + isSaving = false, + defaultEditing = false, }: PlateEditorProps) { - const lastMarkdownRef = useRef(markdown); + const lastMarkdownRef = useRef(markdown); - // When readOnly is forced, always start in readOnly. - // Otherwise, respect defaultEditing to decide initial mode. - // The user can still toggle between editing/viewing via ModeToolbarButton. - const editor = usePlateEditor({ - readOnly: readOnly || !defaultEditing, - plugins: [ - ...BasicNodesKit, - ...TableKit, - ...ListKit, - ...CodeBlockKit, - ...LinkKit, - ...CalloutKit, - ...ToggleKit, - ...MathKit, - ...SelectionKit, - ...SlashCommandKit, - ...FixedToolbarKit, - ...FloatingToolbarKit, - ...AutoformatKit, - ...DndKit, - MarkdownPlugin.configure({ - options: { - remarkPlugins: [remarkGfm, remarkMath, remarkMdx], - }, - }), - ], - // Use markdown deserialization for initial value if provided - value: markdown - ? (editor) => - editor - .getApi(MarkdownPlugin) - .markdown.deserialize(escapeMdxExpressions(markdown)) - : undefined, - }); + // When readOnly is forced, always start in readOnly. + // Otherwise, respect defaultEditing to decide initial mode. + // The user can still toggle between editing/viewing via ModeToolbarButton. + const editor = usePlateEditor({ + readOnly: readOnly || !defaultEditing, + plugins: [ + ...BasicNodesKit, + ...TableKit, + ...ListKit, + ...CodeBlockKit, + ...LinkKit, + ...CalloutKit, + ...ToggleKit, + ...MathKit, + ...SelectionKit, + ...SlashCommandKit, + ...FixedToolbarKit, + ...FloatingToolbarKit, + ...AutoformatKit, + ...DndKit, + MarkdownPlugin.configure({ + options: { + remarkPlugins: [remarkGfm, remarkMath, remarkMdx], + }, + }), + ], + // Use markdown deserialization for initial value if provided + value: markdown + ? (editor) => + editor.getApi(MarkdownPlugin).markdown.deserialize(escapeMdxExpressions(markdown)) + : undefined, + }); - // Update editor content when markdown prop changes externally - // (e.g., version switching in report panel) - useEffect(() => { - if (markdown !== undefined && markdown !== lastMarkdownRef.current) { - lastMarkdownRef.current = markdown; - const newValue = editor - .getApi(MarkdownPlugin) - .markdown.deserialize(escapeMdxExpressions(markdown)); - editor.tf.reset(); - editor.tf.setValue(newValue); - } - }, [markdown, editor]); + // Update editor content when markdown prop changes externally + // (e.g., version switching in report panel) + useEffect(() => { + if (markdown !== undefined && markdown !== lastMarkdownRef.current) { + lastMarkdownRef.current = markdown; + const newValue = editor + .getApi(MarkdownPlugin) + .markdown.deserialize(escapeMdxExpressions(markdown)); + editor.tf.reset(); + editor.tf.setValue(newValue); + } + }, [markdown, editor]); - // When not forced read-only, the user can toggle between editing/viewing. - const canToggleMode = !readOnly; + // When not forced read-only, the user can toggle between editing/viewing. + const canToggleMode = !readOnly; - return ( - - { - if (onMarkdownChange) { - const md = editor.getApi(MarkdownPlugin).markdown.serialize({ value }); - lastMarkdownRef.current = md; - onMarkdownChange(md); - } - }} - > - - - - - - ); + return ( + + { + if (onMarkdownChange) { + const md = editor.getApi(MarkdownPlugin).markdown.serialize({ value }); + lastMarkdownRef.current = md; + onMarkdownChange(md); + } + }} + > + + + + + + ); } diff --git a/surfsense_web/components/editor/plugins/autoformat-kit.tsx b/surfsense_web/components/editor/plugins/autoformat-kit.tsx index 60302a1c5..a145fbb94 100644 --- a/surfsense_web/components/editor/plugins/autoformat-kit.tsx +++ b/surfsense_web/components/editor/plugins/autoformat-kit.tsx @@ -1,238 +1,237 @@ -'use client'; +"use client"; -import type { AutoformatRule } from '@platejs/autoformat'; +import type { AutoformatRule } from "@platejs/autoformat"; import { - autoformatArrow, - autoformatLegal, - autoformatLegalHtml, - autoformatMath, - AutoformatPlugin, - autoformatPunctuation, - autoformatSmartQuotes, -} from '@platejs/autoformat'; -import { insertEmptyCodeBlock } from '@platejs/code-block'; -import { toggleList } from '@platejs/list'; -import { openNextToggles } from '@platejs/toggle/react'; -import { KEYS } from 'platejs'; + autoformatArrow, + autoformatLegal, + autoformatLegalHtml, + autoformatMath, + AutoformatPlugin, + autoformatPunctuation, + autoformatSmartQuotes, +} from "@platejs/autoformat"; +import { insertEmptyCodeBlock } from "@platejs/code-block"; +import { toggleList } from "@platejs/list"; +import { openNextToggles } from "@platejs/toggle/react"; +import { KEYS } from "platejs"; const autoformatMarks: AutoformatRule[] = [ - { - match: '***', - mode: 'mark', - type: [KEYS.bold, KEYS.italic], - }, - { - match: '__*', - mode: 'mark', - type: [KEYS.underline, KEYS.italic], - }, - { - match: '__**', - mode: 'mark', - type: [KEYS.underline, KEYS.bold], - }, - { - match: '___***', - mode: 'mark', - type: [KEYS.underline, KEYS.bold, KEYS.italic], - }, - { - match: '**', - mode: 'mark', - type: KEYS.bold, - }, - { - match: '__', - mode: 'mark', - type: KEYS.underline, - }, - { - match: '*', - mode: 'mark', - type: KEYS.italic, - }, - { - match: '_', - mode: 'mark', - type: KEYS.italic, - }, - { - match: '~~', - mode: 'mark', - type: KEYS.strikethrough, - }, - { - match: '^', - mode: 'mark', - type: KEYS.sup, - }, - { - match: '~', - mode: 'mark', - type: KEYS.sub, - }, - { - match: '==', - mode: 'mark', - type: KEYS.highlight, - }, - { - match: '≡', - mode: 'mark', - type: KEYS.highlight, - }, - { - match: '`', - mode: 'mark', - type: KEYS.code, - }, + { + match: "***", + mode: "mark", + type: [KEYS.bold, KEYS.italic], + }, + { + match: "__*", + mode: "mark", + type: [KEYS.underline, KEYS.italic], + }, + { + match: "__**", + mode: "mark", + type: [KEYS.underline, KEYS.bold], + }, + { + match: "___***", + mode: "mark", + type: [KEYS.underline, KEYS.bold, KEYS.italic], + }, + { + match: "**", + mode: "mark", + type: KEYS.bold, + }, + { + match: "__", + mode: "mark", + type: KEYS.underline, + }, + { + match: "*", + mode: "mark", + type: KEYS.italic, + }, + { + match: "_", + mode: "mark", + type: KEYS.italic, + }, + { + match: "~~", + mode: "mark", + type: KEYS.strikethrough, + }, + { + match: "^", + mode: "mark", + type: KEYS.sup, + }, + { + match: "~", + mode: "mark", + type: KEYS.sub, + }, + { + match: "==", + mode: "mark", + type: KEYS.highlight, + }, + { + match: "≡", + mode: "mark", + type: KEYS.highlight, + }, + { + match: "`", + mode: "mark", + type: KEYS.code, + }, ]; const autoformatBlocks: AutoformatRule[] = [ - { - match: '# ', - mode: 'block', - type: KEYS.h1, - }, - { - match: '## ', - mode: 'block', - type: KEYS.h2, - }, - { - match: '### ', - mode: 'block', - type: KEYS.h3, - }, - { - match: '#### ', - mode: 'block', - type: KEYS.h4, - }, - { - match: '##### ', - mode: 'block', - type: KEYS.h5, - }, - { - match: '###### ', - mode: 'block', - type: KEYS.h6, - }, - { - match: '> ', - mode: 'block', - type: KEYS.blockquote, - }, - { - match: '```', - mode: 'block', - type: KEYS.codeBlock, - format: (editor) => { - insertEmptyCodeBlock(editor, { - defaultType: KEYS.p, - insertNodesOptions: { select: true }, - }); - }, - }, - { - match: '+ ', - mode: 'block', - preFormat: openNextToggles, - type: KEYS.toggle, - }, - { - match: ['---', '—-', '___ '], - mode: 'block', - type: KEYS.hr, - format: (editor) => { - editor.tf.setNodes({ type: KEYS.hr }); - editor.tf.insertNodes({ - children: [{ text: '' }], - type: KEYS.p, - }); - }, - }, + { + match: "# ", + mode: "block", + type: KEYS.h1, + }, + { + match: "## ", + mode: "block", + type: KEYS.h2, + }, + { + match: "### ", + mode: "block", + type: KEYS.h3, + }, + { + match: "#### ", + mode: "block", + type: KEYS.h4, + }, + { + match: "##### ", + mode: "block", + type: KEYS.h5, + }, + { + match: "###### ", + mode: "block", + type: KEYS.h6, + }, + { + match: "> ", + mode: "block", + type: KEYS.blockquote, + }, + { + match: "```", + mode: "block", + type: KEYS.codeBlock, + format: (editor) => { + insertEmptyCodeBlock(editor, { + defaultType: KEYS.p, + insertNodesOptions: { select: true }, + }); + }, + }, + { + match: "+ ", + mode: "block", + preFormat: openNextToggles, + type: KEYS.toggle, + }, + { + match: ["---", "—-", "___ "], + mode: "block", + type: KEYS.hr, + format: (editor) => { + editor.tf.setNodes({ type: KEYS.hr }); + editor.tf.insertNodes({ + children: [{ text: "" }], + type: KEYS.p, + }); + }, + }, ]; const autoformatLists: AutoformatRule[] = [ - { - match: ['* ', '- '], - mode: 'block', - type: 'list', - format: (editor) => { - toggleList(editor, { - listStyleType: KEYS.ul, - }); - }, - }, - { - match: [String.raw`^\d+\.$ `, String.raw`^\d+\)$ `], - matchByRegex: true, - mode: 'block', - type: 'list', - format: (editor, { matchString }) => { - toggleList(editor, { - listRestartPolite: Number(matchString) || 1, - listStyleType: KEYS.ol, - }); - }, - }, - { - match: ['[] '], - mode: 'block', - type: 'list', - format: (editor) => { - toggleList(editor, { - listStyleType: KEYS.listTodo, - }); - editor.tf.setNodes({ - checked: false, - listStyleType: KEYS.listTodo, - }); - }, - }, - { - match: ['[x] '], - mode: 'block', - type: 'list', - format: (editor) => { - toggleList(editor, { - listStyleType: KEYS.listTodo, - }); - editor.tf.setNodes({ - checked: true, - listStyleType: KEYS.listTodo, - }); - }, - }, + { + match: ["* ", "- "], + mode: "block", + type: "list", + format: (editor) => { + toggleList(editor, { + listStyleType: KEYS.ul, + }); + }, + }, + { + match: [String.raw`^\d+\.$ `, String.raw`^\d+\)$ `], + matchByRegex: true, + mode: "block", + type: "list", + format: (editor, { matchString }) => { + toggleList(editor, { + listRestartPolite: Number(matchString) || 1, + listStyleType: KEYS.ol, + }); + }, + }, + { + match: ["[] "], + mode: "block", + type: "list", + format: (editor) => { + toggleList(editor, { + listStyleType: KEYS.listTodo, + }); + editor.tf.setNodes({ + checked: false, + listStyleType: KEYS.listTodo, + }); + }, + }, + { + match: ["[x] "], + mode: "block", + type: "list", + format: (editor) => { + toggleList(editor, { + listStyleType: KEYS.listTodo, + }); + editor.tf.setNodes({ + checked: true, + listStyleType: KEYS.listTodo, + }); + }, + }, ]; export const AutoformatKit = [ - AutoformatPlugin.configure({ - options: { - enableUndoOnDelete: true, - rules: [ - ...autoformatBlocks, - ...autoformatMarks, - ...autoformatSmartQuotes, - ...autoformatPunctuation, - ...autoformatLegal, - ...autoformatLegalHtml, - ...autoformatArrow, - ...autoformatMath, - ...autoformatLists, - ].map( - (rule): AutoformatRule => ({ - ...rule, - query: (editor) => - !editor.api.some({ - match: { type: editor.getType(KEYS.codeBlock) }, - }), - }) - ), - }, - }), + AutoformatPlugin.configure({ + options: { + enableUndoOnDelete: true, + rules: [ + ...autoformatBlocks, + ...autoformatMarks, + ...autoformatSmartQuotes, + ...autoformatPunctuation, + ...autoformatLegal, + ...autoformatLegalHtml, + ...autoformatArrow, + ...autoformatMath, + ...autoformatLists, + ].map( + (rule): AutoformatRule => ({ + ...rule, + query: (editor) => + !editor.api.some({ + match: { type: editor.getType(KEYS.codeBlock) }, + }), + }) + ), + }, + }), ]; - diff --git a/surfsense_web/components/editor/plugins/basic-blocks-kit.tsx b/surfsense_web/components/editor/plugins/basic-blocks-kit.tsx index ddd61f326..660648baf 100644 --- a/surfsense_web/components/editor/plugins/basic-blocks-kit.tsx +++ b/surfsense_web/components/editor/plugins/basic-blocks-kit.tsx @@ -1,86 +1,86 @@ -'use client'; +"use client"; import { - BlockquotePlugin, - H1Plugin, - H2Plugin, - H3Plugin, - H4Plugin, - H5Plugin, - H6Plugin, - HorizontalRulePlugin, -} from '@platejs/basic-nodes/react'; -import { ParagraphPlugin } from 'platejs/react'; + BlockquotePlugin, + H1Plugin, + H2Plugin, + H3Plugin, + H4Plugin, + H5Plugin, + H6Plugin, + HorizontalRulePlugin, +} from "@platejs/basic-nodes/react"; +import { ParagraphPlugin } from "platejs/react"; -import { BlockquoteElement } from '@/components/ui/blockquote-node'; +import { BlockquoteElement } from "@/components/ui/blockquote-node"; import { - H1Element, - H2Element, - H3Element, - H4Element, - H5Element, - H6Element, -} from '@/components/ui/heading-node'; -import { HrElement } from '@/components/ui/hr-node'; -import { ParagraphElement } from '@/components/ui/paragraph-node'; + H1Element, + H2Element, + H3Element, + H4Element, + H5Element, + H6Element, +} from "@/components/ui/heading-node"; +import { HrElement } from "@/components/ui/hr-node"; +import { ParagraphElement } from "@/components/ui/paragraph-node"; export const BasicBlocksKit = [ - ParagraphPlugin.withComponent(ParagraphElement), - H1Plugin.configure({ - node: { - component: H1Element, - }, - rules: { - break: { empty: 'reset' }, - }, - shortcuts: { toggle: { keys: 'mod+alt+1' } }, - }), - H2Plugin.configure({ - node: { - component: H2Element, - }, - rules: { - break: { empty: 'reset' }, - }, - shortcuts: { toggle: { keys: 'mod+alt+2' } }, - }), - H3Plugin.configure({ - node: { - component: H3Element, - }, - rules: { - break: { empty: 'reset' }, - }, - shortcuts: { toggle: { keys: 'mod+alt+3' } }, - }), - H4Plugin.configure({ - node: { - component: H4Element, - }, - rules: { - break: { empty: 'reset' }, - }, - shortcuts: { toggle: { keys: 'mod+alt+4' } }, - }), - H5Plugin.configure({ - node: { - component: H5Element, - }, - rules: { - break: { empty: 'reset' }, - }, - }), - H6Plugin.configure({ - node: { - component: H6Element, - }, - rules: { - break: { empty: 'reset' }, - }, - }), - BlockquotePlugin.configure({ - node: { component: BlockquoteElement }, - shortcuts: { toggle: { keys: 'mod+shift+period' } }, - }), - HorizontalRulePlugin.withComponent(HrElement), + ParagraphPlugin.withComponent(ParagraphElement), + H1Plugin.configure({ + node: { + component: H1Element, + }, + rules: { + break: { empty: "reset" }, + }, + shortcuts: { toggle: { keys: "mod+alt+1" } }, + }), + H2Plugin.configure({ + node: { + component: H2Element, + }, + rules: { + break: { empty: "reset" }, + }, + shortcuts: { toggle: { keys: "mod+alt+2" } }, + }), + H3Plugin.configure({ + node: { + component: H3Element, + }, + rules: { + break: { empty: "reset" }, + }, + shortcuts: { toggle: { keys: "mod+alt+3" } }, + }), + H4Plugin.configure({ + node: { + component: H4Element, + }, + rules: { + break: { empty: "reset" }, + }, + shortcuts: { toggle: { keys: "mod+alt+4" } }, + }), + H5Plugin.configure({ + node: { + component: H5Element, + }, + rules: { + break: { empty: "reset" }, + }, + }), + H6Plugin.configure({ + node: { + component: H6Element, + }, + rules: { + break: { empty: "reset" }, + }, + }), + BlockquotePlugin.configure({ + node: { component: BlockquoteElement }, + shortcuts: { toggle: { keys: "mod+shift+period" } }, + }), + HorizontalRulePlugin.withComponent(HrElement), ]; diff --git a/surfsense_web/components/editor/plugins/basic-marks-kit.tsx b/surfsense_web/components/editor/plugins/basic-marks-kit.tsx index 4190efc29..308fb9031 100644 --- a/surfsense_web/components/editor/plugins/basic-marks-kit.tsx +++ b/surfsense_web/components/editor/plugins/basic-marks-kit.tsx @@ -1,38 +1,38 @@ -'use client'; +"use client"; import { - BoldPlugin, - CodePlugin, - HighlightPlugin, - ItalicPlugin, - StrikethroughPlugin, - SubscriptPlugin, - SuperscriptPlugin, - UnderlinePlugin, -} from '@platejs/basic-nodes/react'; + BoldPlugin, + CodePlugin, + HighlightPlugin, + ItalicPlugin, + StrikethroughPlugin, + SubscriptPlugin, + SuperscriptPlugin, + UnderlinePlugin, +} from "@platejs/basic-nodes/react"; -import { CodeLeaf } from '@/components/ui/code-node'; -import { HighlightLeaf } from '@/components/ui/highlight-node'; +import { CodeLeaf } from "@/components/ui/code-node"; +import { HighlightLeaf } from "@/components/ui/highlight-node"; export const BasicMarksKit = [ - BoldPlugin, - ItalicPlugin, - UnderlinePlugin, - CodePlugin.configure({ - node: { component: CodeLeaf }, - shortcuts: { toggle: { keys: 'mod+e' } }, - }), - StrikethroughPlugin.configure({ - shortcuts: { toggle: { keys: 'mod+shift+x' } }, - }), - SubscriptPlugin.configure({ - shortcuts: { toggle: { keys: 'mod+comma' } }, - }), - SuperscriptPlugin.configure({ - shortcuts: { toggle: { keys: 'mod+period' } }, - }), - HighlightPlugin.configure({ - node: { component: HighlightLeaf }, - shortcuts: { toggle: { keys: 'mod+shift+h' } }, - }), + BoldPlugin, + ItalicPlugin, + UnderlinePlugin, + CodePlugin.configure({ + node: { component: CodeLeaf }, + shortcuts: { toggle: { keys: "mod+e" } }, + }), + StrikethroughPlugin.configure({ + shortcuts: { toggle: { keys: "mod+shift+x" } }, + }), + SubscriptPlugin.configure({ + shortcuts: { toggle: { keys: "mod+comma" } }, + }), + SuperscriptPlugin.configure({ + shortcuts: { toggle: { keys: "mod+period" } }, + }), + HighlightPlugin.configure({ + node: { component: HighlightLeaf }, + shortcuts: { toggle: { keys: "mod+shift+h" } }, + }), ]; diff --git a/surfsense_web/components/editor/plugins/basic-nodes-kit.tsx b/surfsense_web/components/editor/plugins/basic-nodes-kit.tsx index 6f8341635..6f61868ed 100644 --- a/surfsense_web/components/editor/plugins/basic-nodes-kit.tsx +++ b/surfsense_web/components/editor/plugins/basic-nodes-kit.tsx @@ -1,6 +1,6 @@ -'use client'; +"use client"; -import { BasicBlocksKit } from './basic-blocks-kit'; -import { BasicMarksKit } from './basic-marks-kit'; +import { BasicBlocksKit } from "./basic-blocks-kit"; +import { BasicMarksKit } from "./basic-marks-kit"; export const BasicNodesKit = [...BasicBlocksKit, ...BasicMarksKit]; diff --git a/surfsense_web/components/editor/plugins/callout-kit.tsx b/surfsense_web/components/editor/plugins/callout-kit.tsx index ca862372b..7c3b8b188 100644 --- a/surfsense_web/components/editor/plugins/callout-kit.tsx +++ b/surfsense_web/components/editor/plugins/callout-kit.tsx @@ -1,8 +1,7 @@ -'use client'; +"use client"; -import { CalloutPlugin } from '@platejs/callout/react'; +import { CalloutPlugin } from "@platejs/callout/react"; -import { CalloutElement } from '@/components/ui/callout-node'; +import { CalloutElement } from "@/components/ui/callout-node"; export const CalloutKit = [CalloutPlugin.withComponent(CalloutElement)]; - diff --git a/surfsense_web/components/editor/plugins/code-block-kit.tsx b/surfsense_web/components/editor/plugins/code-block-kit.tsx index 74cb748eb..95b60c073 100644 --- a/surfsense_web/components/editor/plugins/code-block-kit.tsx +++ b/surfsense_web/components/editor/plugins/code-block-kit.tsx @@ -1,26 +1,18 @@ -'use client'; +"use client"; -import { - CodeBlockPlugin, - CodeLinePlugin, - CodeSyntaxPlugin, -} from '@platejs/code-block/react'; -import { all, createLowlight } from 'lowlight'; +import { CodeBlockPlugin, CodeLinePlugin, CodeSyntaxPlugin } from "@platejs/code-block/react"; +import { all, createLowlight } from "lowlight"; -import { - CodeBlockElement, - CodeLineElement, - CodeSyntaxLeaf, -} from '@/components/ui/code-block-node'; +import { CodeBlockElement, CodeLineElement, CodeSyntaxLeaf } from "@/components/ui/code-block-node"; const lowlight = createLowlight(all); export const CodeBlockKit = [ - CodeBlockPlugin.configure({ - node: { component: CodeBlockElement }, - options: { lowlight }, - shortcuts: { toggle: { keys: 'mod+alt+8' } }, - }), - CodeLinePlugin.withComponent(CodeLineElement), - CodeSyntaxPlugin.withComponent(CodeSyntaxLeaf), + CodeBlockPlugin.configure({ + node: { component: CodeBlockElement }, + options: { lowlight }, + shortcuts: { toggle: { keys: "mod+alt+8" } }, + }), + CodeLinePlugin.withComponent(CodeLineElement), + CodeSyntaxPlugin.withComponent(CodeSyntaxLeaf), ]; diff --git a/surfsense_web/components/editor/plugins/dnd-kit.tsx b/surfsense_web/components/editor/plugins/dnd-kit.tsx index 89f1ba31c..0d02c855e 100644 --- a/surfsense_web/components/editor/plugins/dnd-kit.tsx +++ b/surfsense_web/components/editor/plugins/dnd-kit.tsx @@ -1,23 +1,20 @@ -'use client'; +"use client"; -import { DndProvider } from 'react-dnd'; -import { HTML5Backend } from 'react-dnd-html5-backend'; +import { DndProvider } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; -import { DndPlugin } from '@platejs/dnd'; +import { DndPlugin } from "@platejs/dnd"; -import { BlockDraggable } from '@/components/ui/block-draggable'; +import { BlockDraggable } from "@/components/ui/block-draggable"; export const DndKit = [ - DndPlugin.configure({ - options: { - enableScroller: true, - }, - render: { - aboveNodes: BlockDraggable, - aboveSlate: ({ children }) => ( - {children} - ), - }, - }), + DndPlugin.configure({ + options: { + enableScroller: true, + }, + render: { + aboveNodes: BlockDraggable, + aboveSlate: ({ children }) => {children}, + }, + }), ]; - diff --git a/surfsense_web/components/editor/plugins/fixed-toolbar-kit.tsx b/surfsense_web/components/editor/plugins/fixed-toolbar-kit.tsx index 062ee8fef..85e0a08f2 100644 --- a/surfsense_web/components/editor/plugins/fixed-toolbar-kit.tsx +++ b/surfsense_web/components/editor/plugins/fixed-toolbar-kit.tsx @@ -1,20 +1,19 @@ -'use client'; +"use client"; -import { createPlatePlugin } from 'platejs/react'; +import { createPlatePlugin } from "platejs/react"; -import { FixedToolbar } from '@/components/ui/fixed-toolbar'; -import { FixedToolbarButtons } from '@/components/ui/fixed-toolbar-buttons'; +import { FixedToolbar } from "@/components/ui/fixed-toolbar"; +import { FixedToolbarButtons } from "@/components/ui/fixed-toolbar-buttons"; export const FixedToolbarKit = [ - createPlatePlugin({ - key: 'fixed-toolbar', - render: { - beforeEditable: () => ( - - - - ), - }, - }), + createPlatePlugin({ + key: "fixed-toolbar", + render: { + beforeEditable: () => ( + + + + ), + }, + }), ]; - diff --git a/surfsense_web/components/editor/plugins/floating-toolbar-kit.tsx b/surfsense_web/components/editor/plugins/floating-toolbar-kit.tsx index 924439a64..e0a73e3c2 100644 --- a/surfsense_web/components/editor/plugins/floating-toolbar-kit.tsx +++ b/surfsense_web/components/editor/plugins/floating-toolbar-kit.tsx @@ -1,19 +1,19 @@ -'use client'; +"use client"; -import { createPlatePlugin } from 'platejs/react'; +import { createPlatePlugin } from "platejs/react"; -import { FloatingToolbar } from '@/components/ui/floating-toolbar'; -import { FloatingToolbarButtons } from '@/components/ui/floating-toolbar-buttons'; +import { FloatingToolbar } from "@/components/ui/floating-toolbar"; +import { FloatingToolbarButtons } from "@/components/ui/floating-toolbar-buttons"; export const FloatingToolbarKit = [ - createPlatePlugin({ - key: 'floating-toolbar', - render: { - afterEditable: () => ( - - - - ), - }, - }), + createPlatePlugin({ + key: "floating-toolbar", + render: { + afterEditable: () => ( + + + + ), + }, + }), ]; diff --git a/surfsense_web/components/editor/plugins/indent-kit.tsx b/surfsense_web/components/editor/plugins/indent-kit.tsx index c2adbb2b4..0a86be4ad 100644 --- a/surfsense_web/components/editor/plugins/indent-kit.tsx +++ b/surfsense_web/components/editor/plugins/indent-kit.tsx @@ -1,19 +1,12 @@ -'use client'; +"use client"; -import { IndentPlugin } from '@platejs/indent/react'; -import { KEYS } from 'platejs'; +import { IndentPlugin } from "@platejs/indent/react"; +import { KEYS } from "platejs"; export const IndentKit = [ - IndentPlugin.configure({ - inject: { - targetPlugins: [ - ...KEYS.heading, - KEYS.p, - KEYS.blockquote, - KEYS.codeBlock, - KEYS.toggle, - ], - }, - }), + IndentPlugin.configure({ + inject: { + targetPlugins: [...KEYS.heading, KEYS.p, KEYS.blockquote, KEYS.codeBlock, KEYS.toggle], + }, + }), ]; - diff --git a/surfsense_web/components/editor/plugins/link-kit.tsx b/surfsense_web/components/editor/plugins/link-kit.tsx index 91b1f892d..62e18a60d 100644 --- a/surfsense_web/components/editor/plugins/link-kit.tsx +++ b/surfsense_web/components/editor/plugins/link-kit.tsx @@ -1,15 +1,15 @@ -'use client'; +"use client"; -import { LinkPlugin } from '@platejs/link/react'; +import { LinkPlugin } from "@platejs/link/react"; -import { LinkElement } from '@/components/ui/link-node'; -import { LinkFloatingToolbar } from '@/components/ui/link-toolbar'; +import { LinkElement } from "@/components/ui/link-node"; +import { LinkFloatingToolbar } from "@/components/ui/link-toolbar"; export const LinkKit = [ - LinkPlugin.configure({ - render: { - node: LinkElement, - afterEditable: () => , - }, - }), + LinkPlugin.configure({ + render: { + node: LinkElement, + afterEditable: () => , + }, + }), ]; diff --git a/surfsense_web/components/editor/plugins/list-kit.tsx b/surfsense_web/components/editor/plugins/list-kit.tsx index 559bb3a98..40b31bbf1 100644 --- a/surfsense_web/components/editor/plugins/list-kit.tsx +++ b/surfsense_web/components/editor/plugins/list-kit.tsx @@ -1,26 +1,19 @@ -'use client'; +"use client"; -import { ListPlugin } from '@platejs/list/react'; -import { KEYS } from 'platejs'; +import { ListPlugin } from "@platejs/list/react"; +import { KEYS } from "platejs"; -import { IndentKit } from '@/components/editor/plugins/indent-kit'; -import { BlockList } from '@/components/ui/block-list'; +import { IndentKit } from "@/components/editor/plugins/indent-kit"; +import { BlockList } from "@/components/ui/block-list"; export const ListKit = [ - ...IndentKit, - ListPlugin.configure({ - inject: { - targetPlugins: [ - ...KEYS.heading, - KEYS.p, - KEYS.blockquote, - KEYS.codeBlock, - KEYS.toggle, - ], - }, - render: { - belowNodes: BlockList, - }, - }), + ...IndentKit, + ListPlugin.configure({ + inject: { + targetPlugins: [...KEYS.heading, KEYS.p, KEYS.blockquote, KEYS.codeBlock, KEYS.toggle], + }, + render: { + belowNodes: BlockList, + }, + }), ]; - diff --git a/surfsense_web/components/editor/plugins/math-kit.tsx b/surfsense_web/components/editor/plugins/math-kit.tsx index 118fa6c47..9f31df374 100644 --- a/surfsense_web/components/editor/plugins/math-kit.tsx +++ b/surfsense_web/components/editor/plugins/math-kit.tsx @@ -1,11 +1,10 @@ -'use client'; +"use client"; -import { EquationPlugin, InlineEquationPlugin } from '@platejs/math/react'; +import { EquationPlugin, InlineEquationPlugin } from "@platejs/math/react"; -import { EquationElement, InlineEquationElement } from '@/components/ui/equation-node'; +import { EquationElement, InlineEquationElement } from "@/components/ui/equation-node"; export const MathKit = [ - EquationPlugin.withComponent(EquationElement), - InlineEquationPlugin.withComponent(InlineEquationElement), + EquationPlugin.withComponent(EquationElement), + InlineEquationPlugin.withComponent(InlineEquationElement), ]; - diff --git a/surfsense_web/components/editor/plugins/selection-kit.tsx b/surfsense_web/components/editor/plugins/selection-kit.tsx index d010ddfca..824426b23 100644 --- a/surfsense_web/components/editor/plugins/selection-kit.tsx +++ b/surfsense_web/components/editor/plugins/selection-kit.tsx @@ -1,23 +1,23 @@ -'use client'; +"use client"; -import { BlockSelectionPlugin } from '@platejs/selection/react'; +import { BlockSelectionPlugin } from "@platejs/selection/react"; -import { BlockSelection } from '@/components/ui/block-selection'; +import { BlockSelection } from "@/components/ui/block-selection"; export const SelectionKit = [ - BlockSelectionPlugin.configure({ - render: { - belowRootNodes: BlockSelection as any, - }, - options: { - isSelectable: (element) => { - // Exclude specific block types from selection - if (['code_line', 'td', 'th'].includes(element.type as string)) { - return false; - } + BlockSelectionPlugin.configure({ + render: { + belowRootNodes: BlockSelection as any, + }, + options: { + isSelectable: (element) => { + // Exclude specific block types from selection + if (["code_line", "td", "th"].includes(element.type as string)) { + return false; + } - return true; - }, - }, - }), + return true; + }, + }, + }), ]; diff --git a/surfsense_web/components/editor/plugins/slash-command-kit.tsx b/surfsense_web/components/editor/plugins/slash-command-kit.tsx index 3d33b904d..ba07c6182 100644 --- a/surfsense_web/components/editor/plugins/slash-command-kit.tsx +++ b/surfsense_web/components/editor/plugins/slash-command-kit.tsx @@ -1,21 +1,20 @@ -'use client'; +"use client"; -import { SlashInputPlugin, SlashPlugin } from '@platejs/slash-command/react'; -import { KEYS } from 'platejs'; +import { SlashInputPlugin, SlashPlugin } from "@platejs/slash-command/react"; +import { KEYS } from "platejs"; -import { SlashInputElement } from '@/components/ui/slash-node'; +import { SlashInputElement } from "@/components/ui/slash-node"; export const SlashCommandKit = [ - SlashPlugin.configure({ - options: { - trigger: '/', - triggerPreviousCharPattern: /^\s?$/, - triggerQuery: (editor) => - !editor.api.some({ - match: { type: editor.getType(KEYS.codeBlock) }, - }), - }, - }), - SlashInputPlugin.withComponent(SlashInputElement), + SlashPlugin.configure({ + options: { + trigger: "/", + triggerPreviousCharPattern: /^\s?$/, + triggerQuery: (editor) => + !editor.api.some({ + match: { type: editor.getType(KEYS.codeBlock) }, + }), + }, + }), + SlashInputPlugin.withComponent(SlashInputElement), ]; - diff --git a/surfsense_web/components/editor/plugins/table-kit.tsx b/surfsense_web/components/editor/plugins/table-kit.tsx index e0b54f1e4..ad111358b 100644 --- a/surfsense_web/components/editor/plugins/table-kit.tsx +++ b/surfsense_web/components/editor/plugins/table-kit.tsx @@ -1,22 +1,22 @@ -'use client'; +"use client"; import { - TableCellHeaderPlugin, - TableCellPlugin, - TablePlugin, - TableRowPlugin, -} from '@platejs/table/react'; + TableCellHeaderPlugin, + TableCellPlugin, + TablePlugin, + TableRowPlugin, +} from "@platejs/table/react"; import { - TableCellElement, - TableCellHeaderElement, - TableElement, - TableRowElement, -} from '@/components/ui/table-node'; + TableCellElement, + TableCellHeaderElement, + TableElement, + TableRowElement, +} from "@/components/ui/table-node"; export const TableKit = [ - TablePlugin.withComponent(TableElement), - TableRowPlugin.withComponent(TableRowElement), - TableCellPlugin.withComponent(TableCellElement), - TableCellHeaderPlugin.withComponent(TableCellHeaderElement), + TablePlugin.withComponent(TableElement), + TableRowPlugin.withComponent(TableRowElement), + TableCellPlugin.withComponent(TableCellElement), + TableCellHeaderPlugin.withComponent(TableCellHeaderElement), ]; diff --git a/surfsense_web/components/editor/plugins/toggle-kit.tsx b/surfsense_web/components/editor/plugins/toggle-kit.tsx index c19d10a37..60f71724c 100644 --- a/surfsense_web/components/editor/plugins/toggle-kit.tsx +++ b/surfsense_web/components/editor/plugins/toggle-kit.tsx @@ -1,13 +1,12 @@ -'use client'; +"use client"; -import { TogglePlugin } from '@platejs/toggle/react'; +import { TogglePlugin } from "@platejs/toggle/react"; -import { ToggleElement } from '@/components/ui/toggle-node'; +import { ToggleElement } from "@/components/ui/toggle-node"; export const ToggleKit = [ - TogglePlugin.configure({ - node: { component: ToggleElement }, - shortcuts: { toggle: { keys: 'mod+alt+9' } }, - }), + TogglePlugin.configure({ + node: { component: ToggleElement }, + shortcuts: { toggle: { keys: "mod+alt+9" } }, + }), ]; - diff --git a/surfsense_web/components/editor/transforms.ts b/surfsense_web/components/editor/transforms.ts index 84359d69e..5f74c9673 100644 --- a/surfsense_web/components/editor/transforms.ts +++ b/surfsense_web/components/editor/transforms.ts @@ -1,184 +1,160 @@ -'use client'; +"use client"; -import type { PlateEditor } from 'platejs/react'; +import type { PlateEditor } from "platejs/react"; -import { insertCallout } from '@platejs/callout'; -import { insertCodeBlock, toggleCodeBlock } from '@platejs/code-block'; -import { triggerFloatingLink } from '@platejs/link/react'; -import { insertInlineEquation } from '@platejs/math'; -import { TablePlugin } from '@platejs/table/react'; -import { - type NodeEntry, - type Path, - type TElement, - KEYS, - PathApi, -} from 'platejs'; +import { insertCallout } from "@platejs/callout"; +import { insertCodeBlock, toggleCodeBlock } from "@platejs/code-block"; +import { triggerFloatingLink } from "@platejs/link/react"; +import { insertInlineEquation } from "@platejs/math"; +import { TablePlugin } from "@platejs/table/react"; +import { type NodeEntry, type Path, type TElement, KEYS, PathApi } from "platejs"; const insertList = (editor: PlateEditor, type: string) => { - editor.tf.insertNodes( - editor.api.create.block({ - indent: 1, - listStyleType: type, - }), - { select: true } - ); + editor.tf.insertNodes( + editor.api.create.block({ + indent: 1, + listStyleType: type, + }), + { select: true } + ); }; -const insertBlockMap: Record< - string, - (editor: PlateEditor, type: string) => void -> = { - [KEYS.listTodo]: insertList, - [KEYS.ol]: insertList, - [KEYS.ul]: insertList, - [KEYS.codeBlock]: (editor) => insertCodeBlock(editor, { select: true }), - [KEYS.table]: (editor) => - editor.getTransforms(TablePlugin).insert.table({}, { select: true }), - [KEYS.callout]: (editor) => insertCallout(editor, { select: true }), - [KEYS.toggle]: (editor) => { - editor.tf.insertNodes( - editor.api.create.block({ type: KEYS.toggle }), - { select: true } - ); - }, +const insertBlockMap: Record void> = { + [KEYS.listTodo]: insertList, + [KEYS.ol]: insertList, + [KEYS.ul]: insertList, + [KEYS.codeBlock]: (editor) => insertCodeBlock(editor, { select: true }), + [KEYS.table]: (editor) => editor.getTransforms(TablePlugin).insert.table({}, { select: true }), + [KEYS.callout]: (editor) => insertCallout(editor, { select: true }), + [KEYS.toggle]: (editor) => { + editor.tf.insertNodes(editor.api.create.block({ type: KEYS.toggle }), { select: true }); + }, }; -const insertInlineMap: Record< - string, - (editor: PlateEditor, type: string) => void -> = { - [KEYS.link]: (editor) => triggerFloatingLink(editor, { focused: true }), - [KEYS.equation]: (editor) => insertInlineEquation(editor), +const insertInlineMap: Record void> = { + [KEYS.link]: (editor) => triggerFloatingLink(editor, { focused: true }), + [KEYS.equation]: (editor) => insertInlineEquation(editor), }; type InsertBlockOptions = { - upsert?: boolean; + upsert?: boolean; }; export const insertBlock = ( - editor: PlateEditor, - type: string, - options: InsertBlockOptions = {} + editor: PlateEditor, + type: string, + options: InsertBlockOptions = {} ) => { - const { upsert = false } = options; + const { upsert = false } = options; - editor.tf.withoutNormalizing(() => { - const block = editor.api.block(); + editor.tf.withoutNormalizing(() => { + const block = editor.api.block(); - if (!block) return; + if (!block) return; - const [currentNode, path] = block; - const isCurrentBlockEmpty = editor.api.isEmpty(currentNode); - const currentBlockType = getBlockType(currentNode); + const [currentNode, path] = block; + const isCurrentBlockEmpty = editor.api.isEmpty(currentNode); + const currentBlockType = getBlockType(currentNode); - const isSameBlockType = type === currentBlockType; + const isSameBlockType = type === currentBlockType; - if (upsert && isCurrentBlockEmpty && isSameBlockType) { - return; - } + if (upsert && isCurrentBlockEmpty && isSameBlockType) { + return; + } - if (type in insertBlockMap) { - insertBlockMap[type](editor, type); - } else { - editor.tf.insertNodes(editor.api.create.block({ type }), { - at: PathApi.next(path), - select: true, - }); - } + if (type in insertBlockMap) { + insertBlockMap[type](editor, type); + } else { + editor.tf.insertNodes(editor.api.create.block({ type }), { + at: PathApi.next(path), + select: true, + }); + } - if (!isSameBlockType) { - editor.tf.removeNodes({ previousEmptyBlock: true }); - } - }); + if (!isSameBlockType) { + editor.tf.removeNodes({ previousEmptyBlock: true }); + } + }); }; export const insertInlineElement = (editor: PlateEditor, type: string) => { - if (insertInlineMap[type]) { - insertInlineMap[type](editor, type); - } + if (insertInlineMap[type]) { + insertInlineMap[type](editor, type); + } }; -const setList = ( - editor: PlateEditor, - type: string, - entry: NodeEntry -) => { - editor.tf.setNodes( - editor.api.create.block({ - indent: 1, - listStyleType: type, - }), - { - at: entry[1], - } - ); +const setList = (editor: PlateEditor, type: string, entry: NodeEntry) => { + editor.tf.setNodes( + editor.api.create.block({ + indent: 1, + listStyleType: type, + }), + { + at: entry[1], + } + ); }; const setBlockMap: Record< - string, - (editor: PlateEditor, type: string, entry: NodeEntry) => void + string, + (editor: PlateEditor, type: string, entry: NodeEntry) => void > = { - [KEYS.listTodo]: setList, - [KEYS.ol]: setList, - [KEYS.ul]: setList, - [KEYS.codeBlock]: (editor) => toggleCodeBlock(editor), - [KEYS.callout]: (editor, _type, entry) => { - editor.tf.setNodes({ type: KEYS.callout }, { at: entry[1] }); - }, - [KEYS.toggle]: (editor, _type, entry) => { - editor.tf.setNodes({ type: KEYS.toggle }, { at: entry[1] }); - }, + [KEYS.listTodo]: setList, + [KEYS.ol]: setList, + [KEYS.ul]: setList, + [KEYS.codeBlock]: (editor) => toggleCodeBlock(editor), + [KEYS.callout]: (editor, _type, entry) => { + editor.tf.setNodes({ type: KEYS.callout }, { at: entry[1] }); + }, + [KEYS.toggle]: (editor, _type, entry) => { + editor.tf.setNodes({ type: KEYS.toggle }, { at: entry[1] }); + }, }; -export const setBlockType = ( - editor: PlateEditor, - type: string, - { at }: { at?: Path } = {} -) => { - editor.tf.withoutNormalizing(() => { - const setEntry = (entry: NodeEntry) => { - const [node, path] = entry; +export const setBlockType = (editor: PlateEditor, type: string, { at }: { at?: Path } = {}) => { + editor.tf.withoutNormalizing(() => { + const setEntry = (entry: NodeEntry) => { + const [node, path] = entry; - if (node[KEYS.listType]) { - editor.tf.unsetNodes([KEYS.listType, 'indent'], { at: path }); - } - if (type in setBlockMap) { - return setBlockMap[type](editor, type, entry); - } - if (node.type !== type) { - editor.tf.setNodes({ type }, { at: path }); - } - }; + if (node[KEYS.listType]) { + editor.tf.unsetNodes([KEYS.listType, "indent"], { at: path }); + } + if (type in setBlockMap) { + return setBlockMap[type](editor, type, entry); + } + if (node.type !== type) { + editor.tf.setNodes({ type }, { at: path }); + } + }; - if (at) { - const entry = editor.api.node(at); + if (at) { + const entry = editor.api.node(at); - if (entry) { - setEntry(entry); + if (entry) { + setEntry(entry); - return; - } - } + return; + } + } - const entries = editor.api.blocks({ mode: 'lowest' }); + const entries = editor.api.blocks({ mode: "lowest" }); - entries.forEach((entry) => { - setEntry(entry); - }); - }); + entries.forEach((entry) => { + setEntry(entry); + }); + }); }; export const getBlockType = (block: TElement) => { - if (block[KEYS.listType]) { - if (block[KEYS.listType] === KEYS.ol) { - return KEYS.ol; - } - if (block[KEYS.listType] === KEYS.listTodo) { - return KEYS.listTodo; - } - return KEYS.ul; - } + if (block[KEYS.listType]) { + if (block[KEYS.listType] === KEYS.ol) { + return KEYS.ol; + } + if (block[KEYS.listType] === KEYS.listTodo) { + return KEYS.listTodo; + } + return KEYS.ul; + } - return block.type; + return block.type; }; diff --git a/surfsense_web/components/editor/utils/escape-mdx.ts b/surfsense_web/components/editor/utils/escape-mdx.ts index 7835ef7ac..41e8c9d0a 100644 --- a/surfsense_web/components/editor/utils/escape-mdx.ts +++ b/surfsense_web/components/editor/utils/escape-mdx.ts @@ -12,15 +12,14 @@ const FENCED_OR_INLINE_CODE = /(```[\s\S]*?```|`[^`\n]+`)/g; export function escapeMdxExpressions(md: string): string { - const parts = md.split(FENCED_OR_INLINE_CODE); + const parts = md.split(FENCED_OR_INLINE_CODE); - return parts - .map((part, i) => { - // Odd indices are code blocks / inline code – leave untouched - if (i % 2 === 1) return part; - // Escape { and } that are NOT already escaped (no preceding \) - return part.replace(/(? { + // Odd indices are code blocks / inline code – leave untouched + if (i % 2 === 1) return part; + // Escape { and } that are NOT already escaped (no preceding \) + return part.replace(/(?
- {/* Copy button */} + {/* Copy button */}
) : reportContent.content ? ( - + ) : (

No content available.

@@ -499,7 +499,10 @@ function MobileReportDrawer() { }} shouldScaleBackground={false} > - + {panelState.title || "Report"}
diff --git a/surfsense_web/components/ui/block-draggable.tsx b/surfsense_web/components/ui/block-draggable.tsx index f148e38c8..21405ebff 100644 --- a/surfsense_web/components/ui/block-draggable.tsx +++ b/surfsense_web/components/ui/block-draggable.tsx @@ -1,513 +1,467 @@ -'use client'; +"use client"; -import * as React from 'react'; +import * as React from "react"; -import { DndPlugin, useDraggable, useDropLine } from '@platejs/dnd'; -import { expandListItemsWithChildren } from '@platejs/list'; -import { BlockSelectionPlugin } from '@platejs/selection/react'; -import { GripVertical } from 'lucide-react'; -import { type TElement, getPluginByType, isType, KEYS } from 'platejs'; +import { DndPlugin, useDraggable, useDropLine } from "@platejs/dnd"; +import { expandListItemsWithChildren } from "@platejs/list"; +import { BlockSelectionPlugin } from "@platejs/selection/react"; +import { GripVertical } from "lucide-react"; +import { type TElement, getPluginByType, isType, KEYS } from "platejs"; import { - type PlateEditor, - type PlateElementProps, - type RenderNodeWrapper, - MemoizedChildren, - useEditorRef, - useElement, - usePluginOption, -} from 'platejs/react'; -import { useSelected } from 'platejs/react'; + type PlateEditor, + type PlateElementProps, + type RenderNodeWrapper, + MemoizedChildren, + useEditorRef, + useElement, + usePluginOption, +} from "platejs/react"; +import { useSelected } from "platejs/react"; -import { Button } from '@/components/ui/button'; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from '@/components/ui/tooltip'; -import { cn } from '@/lib/utils'; +import { Button } from "@/components/ui/button"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; const UNDRAGGABLE_KEYS = [KEYS.column, KEYS.tr, KEYS.td]; export const BlockDraggable: RenderNodeWrapper = (props) => { - const { editor, element, path } = props; + const { editor, element, path } = props; - const enabled = React.useMemo(() => { - if (editor.dom.readOnly) return false; + const enabled = React.useMemo(() => { + if (editor.dom.readOnly) return false; - if (path.length === 1 && !isType(editor, element, UNDRAGGABLE_KEYS)) { - return true; - } - if (path.length === 3 && !isType(editor, element, UNDRAGGABLE_KEYS)) { - const block = editor.api.some({ - at: path, - match: { - type: editor.getType(KEYS.column), - }, - }); + if (path.length === 1 && !isType(editor, element, UNDRAGGABLE_KEYS)) { + return true; + } + if (path.length === 3 && !isType(editor, element, UNDRAGGABLE_KEYS)) { + const block = editor.api.some({ + at: path, + match: { + type: editor.getType(KEYS.column), + }, + }); - if (block) { - return true; - } - } - if (path.length === 4 && !isType(editor, element, UNDRAGGABLE_KEYS)) { - const block = editor.api.some({ - at: path, - match: { - type: editor.getType(KEYS.table), - }, - }); + if (block) { + return true; + } + } + if (path.length === 4 && !isType(editor, element, UNDRAGGABLE_KEYS)) { + const block = editor.api.some({ + at: path, + match: { + type: editor.getType(KEYS.table), + }, + }); - if (block) { - return true; - } - } + if (block) { + return true; + } + } - return false; - }, [editor, element, path]); + return false; + }, [editor, element, path]); - if (!enabled) return; + if (!enabled) return; - return (props) => ; + return (props) => ; }; function Draggable(props: PlateElementProps) { - const { children, editor, element, path } = props; - const blockSelectionApi = editor.getApi(BlockSelectionPlugin).blockSelection; + const { children, editor, element, path } = props; + const blockSelectionApi = editor.getApi(BlockSelectionPlugin).blockSelection; - const { isAboutToDrag, isDragging, nodeRef, previewRef, handleRef } = - useDraggable({ - element, - onDropHandler: (_, { dragItem }) => { - const id = (dragItem as { id: string[] | string }).id; + const { isAboutToDrag, isDragging, nodeRef, previewRef, handleRef } = useDraggable({ + element, + onDropHandler: (_, { dragItem }) => { + const id = (dragItem as { id: string[] | string }).id; - if (blockSelectionApi) { - blockSelectionApi.add(id); - } - resetPreview(); - }, - }); + if (blockSelectionApi) { + blockSelectionApi.add(id); + } + resetPreview(); + }, + }); - const isInColumn = path.length === 3; - const isInTable = path.length === 4; + const isInColumn = path.length === 3; + const isInTable = path.length === 4; - const [previewTop, setPreviewTop] = React.useState(0); + const [previewTop, setPreviewTop] = React.useState(0); - const resetPreview = () => { - if (previewRef.current) { - previewRef.current.replaceChildren(); - previewRef.current?.classList.add('hidden'); - } - }; + const resetPreview = () => { + if (previewRef.current) { + previewRef.current.replaceChildren(); + previewRef.current?.classList.add("hidden"); + } + }; - // clear up virtual multiple preview when drag end - React.useEffect(() => { - if (!isDragging) { - resetPreview(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isDragging]); + // clear up virtual multiple preview when drag end + React.useEffect(() => { + if (!isDragging) { + resetPreview(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isDragging]); - React.useEffect(() => { - if (isAboutToDrag) { - previewRef.current?.classList.remove('opacity-0'); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isAboutToDrag]); + React.useEffect(() => { + if (isAboutToDrag) { + previewRef.current?.classList.remove("opacity-0"); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isAboutToDrag]); - const [dragButtonTop, setDragButtonTop] = React.useState(0); + const [dragButtonTop, setDragButtonTop] = React.useState(0); - return ( -
{ - if (isDragging) return; - setDragButtonTop(calcDragButtonTop(editor, element)); - }} - > - {!isInTable && ( - -
-
- -
-
-
- )} + return ( +
{ + if (isDragging) return; + setDragButtonTop(calcDragButtonTop(editor, element)); + }} + > + {!isInTable && ( + +
+
+ +
+
+
+ )} - + ); } -function Gutter({ - children, - className, - ...props -}: React.ComponentProps<'div'>) { - const editor = useEditorRef(); - const element = useElement(); - const isSelectionAreaVisible = usePluginOption( - BlockSelectionPlugin, - 'isSelectionAreaVisible' - ); - const selected = useSelected(); +function Gutter({ children, className, ...props }: React.ComponentProps<"div">) { + const editor = useEditorRef(); + const element = useElement(); + const isSelectionAreaVisible = usePluginOption(BlockSelectionPlugin, "isSelectionAreaVisible"); + const selected = useSelected(); - return ( -
- {children} -
- ); + return ( +
+ {children} +
+ ); } const DragHandle = React.memo(function DragHandle({ - isDragging, - previewRef, - resetPreview, - setPreviewTop, + isDragging, + previewRef, + resetPreview, + setPreviewTop, }: { - isDragging: boolean; - previewRef: React.RefObject; - resetPreview: () => void; - setPreviewTop: (top: number) => void; + isDragging: boolean; + previewRef: React.RefObject; + resetPreview: () => void; + setPreviewTop: (top: number) => void; }) { - const editor = useEditorRef(); - const element = useElement(); + const editor = useEditorRef(); + const element = useElement(); - return ( - - -
{ - e.preventDefault(); - editor.getApi(BlockSelectionPlugin).blockSelection.focus(); - }} - onMouseDown={(e) => { - resetPreview(); + return ( + + +
{ + e.preventDefault(); + editor.getApi(BlockSelectionPlugin).blockSelection.focus(); + }} + onMouseDown={(e) => { + resetPreview(); - if ((e.button !== 0 && e.button !== 2) || e.shiftKey) return; + if ((e.button !== 0 && e.button !== 2) || e.shiftKey) return; - const blockSelection = editor - .getApi(BlockSelectionPlugin) - .blockSelection.getNodes({ sort: true }); + const blockSelection = editor + .getApi(BlockSelectionPlugin) + .blockSelection.getNodes({ sort: true }); - let selectionNodes = - blockSelection.length > 0 - ? blockSelection - : editor.api.blocks({ mode: 'highest' }); + let selectionNodes = + blockSelection.length > 0 ? blockSelection : editor.api.blocks({ mode: "highest" }); - // If current block is not in selection, use it as the starting point - if (!selectionNodes.some(([node]) => node.id === element.id)) { - selectionNodes = [[element, editor.api.findPath(element)!]]; - } + // If current block is not in selection, use it as the starting point + if (!selectionNodes.some(([node]) => node.id === element.id)) { + selectionNodes = [[element, editor.api.findPath(element)!]]; + } - // Process selection nodes to include list children - const blocks = expandListItemsWithChildren( - editor, - selectionNodes - ).map(([node]) => node); + // Process selection nodes to include list children + const blocks = expandListItemsWithChildren(editor, selectionNodes).map( + ([node]) => node + ); - if (blockSelection.length === 0) { - editor.tf.blur(); - editor.tf.collapse(); - } + if (blockSelection.length === 0) { + editor.tf.blur(); + editor.tf.collapse(); + } - const elements = createDragPreviewElements(editor, blocks); - previewRef.current?.append(...elements); - previewRef.current?.classList.remove('hidden'); - previewRef.current?.classList.add('opacity-0'); - editor.setOption(DndPlugin, 'multiplePreviewRef', previewRef); + const elements = createDragPreviewElements(editor, blocks); + previewRef.current?.append(...elements); + previewRef.current?.classList.remove("hidden"); + previewRef.current?.classList.add("opacity-0"); + editor.setOption(DndPlugin, "multiplePreviewRef", previewRef); - editor - .getApi(BlockSelectionPlugin) - .blockSelection.set(blocks.map((block) => block.id as string)); - }} - onMouseEnter={() => { - if (isDragging) return; + editor + .getApi(BlockSelectionPlugin) + .blockSelection.set(blocks.map((block) => block.id as string)); + }} + onMouseEnter={() => { + if (isDragging) return; - const blockSelection = editor - .getApi(BlockSelectionPlugin) - .blockSelection.getNodes({ sort: true }); + const blockSelection = editor + .getApi(BlockSelectionPlugin) + .blockSelection.getNodes({ sort: true }); - let selectedBlocks = - blockSelection.length > 0 - ? blockSelection - : editor.api.blocks({ mode: 'highest' }); + let selectedBlocks = + blockSelection.length > 0 ? blockSelection : editor.api.blocks({ mode: "highest" }); - // If current block is not in selection, use it as the starting point - if (!selectedBlocks.some(([node]) => node.id === element.id)) { - selectedBlocks = [[element, editor.api.findPath(element)!]]; - } + // If current block is not in selection, use it as the starting point + if (!selectedBlocks.some(([node]) => node.id === element.id)) { + selectedBlocks = [[element, editor.api.findPath(element)!]]; + } - // Process selection to include list children - const processedBlocks = expandListItemsWithChildren( - editor, - selectedBlocks - ); + // Process selection to include list children + const processedBlocks = expandListItemsWithChildren(editor, selectedBlocks); - const ids = processedBlocks.map((block) => block[0].id as string); + const ids = processedBlocks.map((block) => block[0].id as string); - if (ids.length > 1 && ids.includes(element.id as string)) { - const previewTop = calculatePreviewTop(editor, { - blocks: processedBlocks.map((block) => block[0]), - element, - }); - setPreviewTop(previewTop); - } else { - setPreviewTop(0); - } - }} - onMouseUp={() => { - resetPreview(); - }} - data-plate-prevent-deselect - role="button" - > - -
-
-
- ); + if (ids.length > 1 && ids.includes(element.id as string)) { + const previewTop = calculatePreviewTop(editor, { + blocks: processedBlocks.map((block) => block[0]), + element, + }); + setPreviewTop(previewTop); + } else { + setPreviewTop(0); + } + }} + onMouseUp={() => { + resetPreview(); + }} + data-plate-prevent-deselect + role="button" + > + +
+
+
+ ); }); const DropLine = React.memo(function DropLine({ - className, - ...props -}: React.ComponentProps<'div'>) { - const { dropLine } = useDropLine(); + className, + ...props +}: React.ComponentProps<"div">) { + const { dropLine } = useDropLine(); - if (!dropLine) return null; + if (!dropLine) return null; - return ( -
- ); + return ( +
+ ); }); -const createDragPreviewElements = ( - editor: PlateEditor, - blocks: TElement[] -): HTMLElement[] => { - const elements: HTMLElement[] = []; - const ids: string[] = []; +const createDragPreviewElements = (editor: PlateEditor, blocks: TElement[]): HTMLElement[] => { + const elements: HTMLElement[] = []; + const ids: string[] = []; - /** - * Remove data attributes from the element to avoid recognized as slate - * elements incorrectly. - */ - const removeDataAttributes = (element: HTMLElement) => { - Array.from(element.attributes).forEach((attr) => { - if ( - attr.name.startsWith('data-slate') || - attr.name.startsWith('data-block-id') - ) { - element.removeAttribute(attr.name); - } - }); + /** + * Remove data attributes from the element to avoid recognized as slate + * elements incorrectly. + */ + const removeDataAttributes = (element: HTMLElement) => { + Array.from(element.attributes).forEach((attr) => { + if (attr.name.startsWith("data-slate") || attr.name.startsWith("data-block-id")) { + element.removeAttribute(attr.name); + } + }); - Array.from(element.children).forEach((child) => { - removeDataAttributes(child as HTMLElement); - }); - }; + Array.from(element.children).forEach((child) => { + removeDataAttributes(child as HTMLElement); + }); + }; - const resolveElement = (node: TElement, index: number) => { - const domNode = editor.api.toDOMNode(node)!; - const newDomNode = domNode.cloneNode(true) as HTMLElement; + const resolveElement = (node: TElement, index: number) => { + const domNode = editor.api.toDOMNode(node)!; + const newDomNode = domNode.cloneNode(true) as HTMLElement; - // Apply visual compensation for horizontal scroll - const applyScrollCompensation = ( - original: Element, - cloned: HTMLElement - ) => { - const scrollLeft = original.scrollLeft; + // Apply visual compensation for horizontal scroll + const applyScrollCompensation = (original: Element, cloned: HTMLElement) => { + const scrollLeft = original.scrollLeft; - if (scrollLeft > 0) { - // Create a wrapper to handle the scroll offset - const scrollWrapper = document.createElement('div'); - scrollWrapper.style.overflow = 'hidden'; - scrollWrapper.style.width = `${original.clientWidth}px`; + if (scrollLeft > 0) { + // Create a wrapper to handle the scroll offset + const scrollWrapper = document.createElement("div"); + scrollWrapper.style.overflow = "hidden"; + scrollWrapper.style.width = `${original.clientWidth}px`; - // Create inner container with the full content - const innerContainer = document.createElement('div'); - innerContainer.style.transform = `translateX(-${scrollLeft}px)`; - innerContainer.style.width = `${original.scrollWidth}px`; + // Create inner container with the full content + const innerContainer = document.createElement("div"); + innerContainer.style.transform = `translateX(-${scrollLeft}px)`; + innerContainer.style.width = `${original.scrollWidth}px`; - // Move all children to the inner container - while (cloned.firstChild) { - innerContainer.append(cloned.firstChild); - } + // Move all children to the inner container + while (cloned.firstChild) { + innerContainer.append(cloned.firstChild); + } - // Apply the original element's styles to maintain appearance - const originalStyles = window.getComputedStyle(original); - cloned.style.padding = '0'; - innerContainer.style.padding = originalStyles.padding; + // Apply the original element's styles to maintain appearance + const originalStyles = window.getComputedStyle(original); + cloned.style.padding = "0"; + innerContainer.style.padding = originalStyles.padding; - scrollWrapper.append(innerContainer); - cloned.append(scrollWrapper); - } - }; + scrollWrapper.append(innerContainer); + cloned.append(scrollWrapper); + } + }; - applyScrollCompensation(domNode, newDomNode); + applyScrollCompensation(domNode, newDomNode); - ids.push(node.id as string); - const wrapper = document.createElement('div'); - wrapper.append(newDomNode); - wrapper.style.display = 'flow-root'; + ids.push(node.id as string); + const wrapper = document.createElement("div"); + wrapper.append(newDomNode); + wrapper.style.display = "flow-root"; - const lastDomNode = blocks[index - 1]; + const lastDomNode = blocks[index - 1]; - if (lastDomNode) { - const lastDomNodeRect = editor.api - .toDOMNode(lastDomNode)! - .parentElement!.getBoundingClientRect(); + if (lastDomNode) { + const lastDomNodeRect = editor.api + .toDOMNode(lastDomNode)! + .parentElement!.getBoundingClientRect(); - const domNodeRect = domNode.parentElement!.getBoundingClientRect(); + const domNodeRect = domNode.parentElement!.getBoundingClientRect(); - const distance = domNodeRect.top - lastDomNodeRect.bottom; + const distance = domNodeRect.top - lastDomNodeRect.bottom; - // Check if the two elements are adjacent (touching each other) - if (distance > 15) { - wrapper.style.marginTop = `${distance}px`; - } - } + // Check if the two elements are adjacent (touching each other) + if (distance > 15) { + wrapper.style.marginTop = `${distance}px`; + } + } - removeDataAttributes(newDomNode); - elements.push(wrapper); - }; + removeDataAttributes(newDomNode); + elements.push(wrapper); + }; - blocks.forEach((node, index) => { - resolveElement(node, index); - }); + blocks.forEach((node, index) => { + resolveElement(node, index); + }); - editor.setOption(DndPlugin, 'draggingId', ids); + editor.setOption(DndPlugin, "draggingId", ids); - return elements; + return elements; }; const calculatePreviewTop = ( - editor: PlateEditor, - { - blocks, - element, - }: { - blocks: TElement[]; - element: TElement; - } + editor: PlateEditor, + { + blocks, + element, + }: { + blocks: TElement[]; + element: TElement; + } ): number => { - const child = editor.api.toDOMNode(element)!; - const editable = editor.api.toDOMNode(editor)!; - const firstSelectedChild = blocks[0]; + const child = editor.api.toDOMNode(element)!; + const editable = editor.api.toDOMNode(editor)!; + const firstSelectedChild = blocks[0]; - const firstDomNode = editor.api.toDOMNode(firstSelectedChild)!; - // Get editor's top padding - const editorPaddingTop = Number( - window.getComputedStyle(editable).paddingTop.replace('px', '') - ); + const firstDomNode = editor.api.toDOMNode(firstSelectedChild)!; + // Get editor's top padding + const editorPaddingTop = Number(window.getComputedStyle(editable).paddingTop.replace("px", "")); - // Calculate distance from first selected node to editor top - const firstNodeToEditorDistance = - firstDomNode.getBoundingClientRect().top - - editable.getBoundingClientRect().top - - editorPaddingTop; + // Calculate distance from first selected node to editor top + const firstNodeToEditorDistance = + firstDomNode.getBoundingClientRect().top - + editable.getBoundingClientRect().top - + editorPaddingTop; - // Get margin top of first selected node - const firstMarginTopString = window.getComputedStyle(firstDomNode).marginTop; - const marginTop = Number(firstMarginTopString.replace('px', '')); + // Get margin top of first selected node + const firstMarginTopString = window.getComputedStyle(firstDomNode).marginTop; + const marginTop = Number(firstMarginTopString.replace("px", "")); - // Calculate distance from current node to editor top - const currentToEditorDistance = - child.getBoundingClientRect().top - - editable.getBoundingClientRect().top - - editorPaddingTop; + // Calculate distance from current node to editor top + const currentToEditorDistance = + child.getBoundingClientRect().top - editable.getBoundingClientRect().top - editorPaddingTop; - const currentMarginTopString = window.getComputedStyle(child).marginTop; - const currentMarginTop = Number(currentMarginTopString.replace('px', '')); + const currentMarginTopString = window.getComputedStyle(child).marginTop; + const currentMarginTop = Number(currentMarginTopString.replace("px", "")); - const previewElementsTopDistance = - currentToEditorDistance - - firstNodeToEditorDistance + - marginTop - - currentMarginTop; + const previewElementsTopDistance = + currentToEditorDistance - firstNodeToEditorDistance + marginTop - currentMarginTop; - return previewElementsTopDistance; + return previewElementsTopDistance; }; const calcDragButtonTop = (editor: PlateEditor, element: TElement): number => { - const child = editor.api.toDOMNode(element)!; + const child = editor.api.toDOMNode(element)!; - const currentMarginTopString = window.getComputedStyle(child).marginTop; - const currentMarginTop = Number(currentMarginTopString.replace('px', '')); + const currentMarginTopString = window.getComputedStyle(child).marginTop; + const currentMarginTop = Number(currentMarginTopString.replace("px", "")); - return currentMarginTop; + return currentMarginTop; }; diff --git a/surfsense_web/components/ui/block-list.tsx b/surfsense_web/components/ui/block-list.tsx index 6cf966327..88bfc0d37 100644 --- a/surfsense_web/components/ui/block-list.tsx +++ b/surfsense_web/components/ui/block-list.tsx @@ -1,87 +1,72 @@ -'use client'; +"use client"; -import React from 'react'; +import React from "react"; -import type { TListElement } from 'platejs'; +import type { TListElement } from "platejs"; -import { isOrderedList } from '@platejs/list'; -import { - useTodoListElement, - useTodoListElementState, -} from '@platejs/list/react'; -import { - type PlateElementProps, - type RenderNodeWrapper, - useReadOnly, -} from 'platejs/react'; +import { isOrderedList } from "@platejs/list"; +import { useTodoListElement, useTodoListElementState } from "@platejs/list/react"; +import { type PlateElementProps, type RenderNodeWrapper, useReadOnly } from "platejs/react"; -import { Checkbox } from '@/components/ui/checkbox'; -import { cn } from '@/lib/utils'; +import { Checkbox } from "@/components/ui/checkbox"; +import { cn } from "@/lib/utils"; const config: Record< - string, - { - Li: React.FC; - Marker: React.FC; - } + string, + { + Li: React.FC; + Marker: React.FC; + } > = { - todo: { - Li: TodoLi, - Marker: TodoMarker, - }, + todo: { + Li: TodoLi, + Marker: TodoMarker, + }, }; export const BlockList: RenderNodeWrapper = (props) => { - if (!props.element.listStyleType) return; + if (!props.element.listStyleType) return; - return (props) => ; + return (props) => ; }; function List(props: PlateElementProps) { - const { listStart, listStyleType } = props.element as TListElement; - const { Li, Marker } = config[listStyleType] ?? {}; - const List = isOrderedList(props.element) ? 'ol' : 'ul'; + const { listStart, listStyleType } = props.element as TListElement; + const { Li, Marker } = config[listStyleType] ?? {}; + const List = isOrderedList(props.element) ? "ol" : "ul"; - return ( - - {Marker && } - {Li ?
  • :
  • {props.children}
  • } -
    - ); + return ( + + {Marker && } + {Li ?
  • :
  • {props.children}
  • } +
    + ); } function TodoMarker(props: PlateElementProps) { - const state = useTodoListElementState({ element: props.element }); - const { checkboxProps } = useTodoListElement(state); - const readOnly = useReadOnly(); + const state = useTodoListElementState({ element: props.element }); + const { checkboxProps } = useTodoListElement(state); + const readOnly = useReadOnly(); - return ( -
    - -
    - ); + return ( +
    + +
    + ); } function TodoLi(props: PlateElementProps) { - return ( -
  • - {props.children} -
  • - ); + return ( +
  • + {props.children} +
  • + ); } diff --git a/surfsense_web/components/ui/block-selection.tsx b/surfsense_web/components/ui/block-selection.tsx index 386fe8852..b8ffdda15 100644 --- a/surfsense_web/components/ui/block-selection.tsx +++ b/surfsense_web/components/ui/block-selection.tsx @@ -1,44 +1,39 @@ -'use client'; +"use client"; -import * as React from 'react'; +import * as React from "react"; -import { DndPlugin } from '@platejs/dnd'; -import { useBlockSelected } from '@platejs/selection/react'; -import { cva } from 'class-variance-authority'; -import { type PlateElementProps, usePluginOption } from 'platejs/react'; +import { DndPlugin } from "@platejs/dnd"; +import { useBlockSelected } from "@platejs/selection/react"; +import { cva } from "class-variance-authority"; +import { type PlateElementProps, usePluginOption } from "platejs/react"; export const blockSelectionVariants = cva( - 'pointer-events-none absolute inset-0 z-1 bg-brand/[.13] transition-opacity', - { - defaultVariants: { - active: true, - }, - variants: { - active: { - false: 'opacity-0', - true: 'opacity-100', - }, - }, - } + "pointer-events-none absolute inset-0 z-1 bg-brand/[.13] transition-opacity", + { + defaultVariants: { + active: true, + }, + variants: { + active: { + false: "opacity-0", + true: "opacity-100", + }, + }, + } ); export function BlockSelection(props: PlateElementProps) { - const isBlockSelected = useBlockSelected(); - const isDragging = usePluginOption(DndPlugin, 'isDragging'); + const isBlockSelected = useBlockSelected(); + const isDragging = usePluginOption(DndPlugin, "isDragging"); - if ( - !isBlockSelected || - props.plugin.key === 'tr' || - props.plugin.key === 'table' - ) - return null; + if (!isBlockSelected || props.plugin.key === "tr" || props.plugin.key === "table") return null; - return ( -
    - ); + return ( +
    + ); } diff --git a/surfsense_web/components/ui/blockquote-node.tsx b/surfsense_web/components/ui/blockquote-node.tsx index ba5bec4e8..1de9b19cf 100644 --- a/surfsense_web/components/ui/blockquote-node.tsx +++ b/surfsense_web/components/ui/blockquote-node.tsx @@ -1,13 +1,7 @@ -'use client'; +"use client"; -import { type PlateElementProps, PlateElement } from 'platejs/react'; +import { type PlateElementProps, PlateElement } from "platejs/react"; export function BlockquoteElement(props: PlateElementProps) { - return ( - - ); + return ; } diff --git a/surfsense_web/components/ui/callout-node.tsx b/surfsense_web/components/ui/callout-node.tsx index 2d24cb864..0972fdf7f 100644 --- a/surfsense_web/components/ui/callout-node.tsx +++ b/surfsense_web/components/ui/callout-node.tsx @@ -1,83 +1,77 @@ -'use client'; +"use client"; -import * as React from 'react'; +import * as React from "react"; -import type { TCalloutElement } from 'platejs'; +import type { TCalloutElement } from "platejs"; -import { CalloutPlugin } from '@platejs/callout/react'; -import { cva } from 'class-variance-authority'; -import { type PlateElementProps, PlateElement, useEditorPlugin } from 'platejs/react'; +import { CalloutPlugin } from "@platejs/callout/react"; +import { cva } from "class-variance-authority"; +import { type PlateElementProps, PlateElement, useEditorPlugin } from "platejs/react"; -import { cn } from '@/lib/utils'; +import { cn } from "@/lib/utils"; -const calloutVariants = cva( - 'my-1 flex w-full items-start gap-2 rounded-lg border p-4', - { - defaultVariants: { - variant: 'info', - }, - variants: { - variant: { - info: 'border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-950/50', - warning: 'border-yellow-200 bg-yellow-50 dark:border-yellow-800 dark:bg-yellow-950/50', - error: 'border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950/50', - success: 'border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950/50', - note: 'border-muted bg-muted/50', - tip: 'border-purple-200 bg-purple-50 dark:border-purple-800 dark:bg-purple-950/50', - }, - }, - } -); +const calloutVariants = cva("my-1 flex w-full items-start gap-2 rounded-lg border p-4", { + defaultVariants: { + variant: "info", + }, + variants: { + variant: { + info: "border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-950/50", + warning: "border-yellow-200 bg-yellow-50 dark:border-yellow-800 dark:bg-yellow-950/50", + error: "border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950/50", + success: "border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950/50", + note: "border-muted bg-muted/50", + tip: "border-purple-200 bg-purple-50 dark:border-purple-800 dark:bg-purple-950/50", + }, + }, +}); const calloutIcons: Record = { - info: '💡', - warning: '⚠️', - error: '🚨', - success: '✅', - note: '📝', - tip: '💜', + info: "💡", + warning: "⚠️", + error: "🚨", + success: "✅", + note: "📝", + tip: "💜", }; -const variantCycle = ['info', 'warning', 'error', 'success', 'note', 'tip'] as const; +const variantCycle = ["info", "warning", "error", "success", "note", "tip"] as const; -export function CalloutElement({ - children, - ...props -}: PlateElementProps) { - const { editor } = useEditorPlugin(CalloutPlugin); - const element = props.element; - const variant = element.variant || 'info'; - const icon = element.icon || calloutIcons[variant] || '💡'; +export function CalloutElement({ children, ...props }: PlateElementProps) { + const { editor } = useEditorPlugin(CalloutPlugin); + const element = props.element; + const variant = element.variant || "info"; + const icon = element.icon || calloutIcons[variant] || "💡"; - const cycleVariant = React.useCallback(() => { - const currentIndex = variantCycle.indexOf(variant as (typeof variantCycle)[number]); - const nextIndex = (currentIndex + 1) % variantCycle.length; - const nextVariant = variantCycle[nextIndex]; + const cycleVariant = React.useCallback(() => { + const currentIndex = variantCycle.indexOf(variant as (typeof variantCycle)[number]); + const nextIndex = (currentIndex + 1) % variantCycle.length; + const nextVariant = variantCycle[nextIndex]; - editor.tf.setNodes( - { - variant: nextVariant, - icon: calloutIcons[nextVariant], - }, - { at: props.path } - ); - }, [editor, variant, props.path]); + editor.tf.setNodes( + { + variant: nextVariant, + icon: calloutIcons[nextVariant], + }, + { at: props.path } + ); + }, [editor, variant, props.path]); - return ( - - -
    {children}
    -
    - ); + return ( + + +
    {children}
    +
    + ); } diff --git a/surfsense_web/components/ui/checkbox.tsx b/surfsense_web/components/ui/checkbox.tsx index a3ec18410..465783c79 100644 --- a/surfsense_web/components/ui/checkbox.tsx +++ b/surfsense_web/components/ui/checkbox.tsx @@ -1,32 +1,29 @@ -"use client" +"use client"; -import * as React from "react" -import { CheckIcon } from "lucide-react" -import { Checkbox as CheckboxPrimitive } from "radix-ui" +import * as React from "react"; +import { CheckIcon } from "lucide-react"; +import { Checkbox as CheckboxPrimitive } from "radix-ui"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -function Checkbox({ - className, - ...props -}: React.ComponentProps) { - return ( - - - - - - ) +function Checkbox({ className, ...props }: React.ComponentProps) { + return ( + + + + + + ); } -export { Checkbox } +export { Checkbox }; diff --git a/surfsense_web/components/ui/code-block-node.tsx b/surfsense_web/components/ui/code-block-node.tsx index b846297db..f0f1c6db7 100644 --- a/surfsense_web/components/ui/code-block-node.tsx +++ b/surfsense_web/components/ui/code-block-node.tsx @@ -1,289 +1,264 @@ -'use client'; +"use client"; -import * as React from 'react'; +import * as React from "react"; -import { formatCodeBlock, isLangSupported } from '@platejs/code-block'; -import { BracesIcon, Check, CheckIcon, CopyIcon } from 'lucide-react'; -import { type TCodeBlockElement, type TCodeSyntaxLeaf, NodeApi } from 'platejs'; +import { formatCodeBlock, isLangSupported } from "@platejs/code-block"; +import { BracesIcon, Check, CheckIcon, CopyIcon } from "lucide-react"; +import { type TCodeBlockElement, type TCodeSyntaxLeaf, NodeApi } from "platejs"; import { - type PlateElementProps, - type PlateLeafProps, - PlateElement, - PlateLeaf, -} from 'platejs/react'; -import { useEditorRef, useElement, useReadOnly } from 'platejs/react'; + type PlateElementProps, + type PlateLeafProps, + PlateElement, + PlateLeaf, +} from "platejs/react"; +import { useEditorRef, useElement, useReadOnly } from "platejs/react"; -import { Button } from '@/components/ui/button'; +import { Button } from "@/components/ui/button"; import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from '@/components/ui/command'; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from '@/components/ui/popover'; -import { cn } from '@/lib/utils'; + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { cn } from "@/lib/utils"; export function CodeBlockElement(props: PlateElementProps) { - const { editor, element } = props; + const { editor, element } = props; - return ( - -
    -
    -          {props.children}
    -        
    + return ( + +
    +
    +					{props.children}
    +				
    -
    - {isLangSupported(element.lang) && ( - - )} +
    + {isLangSupported(element.lang) && ( + + )} - + - NodeApi.string(element)} - /> -
    -
    - - ); + NodeApi.string(element)} + /> +
    +
    +
    + ); } function CodeBlockCombobox() { - const [open, setOpen] = React.useState(false); - const readOnly = useReadOnly(); - const editor = useEditorRef(); - const element = useElement(); - const value = element.lang || 'plaintext'; - const [searchValue, setSearchValue] = React.useState(''); + const [open, setOpen] = React.useState(false); + const readOnly = useReadOnly(); + const editor = useEditorRef(); + const element = useElement(); + const value = element.lang || "plaintext"; + const [searchValue, setSearchValue] = React.useState(""); - const items = React.useMemo( - () => - languages.filter( - (language) => - !searchValue || - language.label.toLowerCase().includes(searchValue.toLowerCase()) - ), - [searchValue] - ); + const items = React.useMemo( + () => + languages.filter( + (language) => + !searchValue || language.label.toLowerCase().includes(searchValue.toLowerCase()) + ), + [searchValue] + ); - if (readOnly) return null; + if (readOnly) return null; - return ( - - - - - setSearchValue('')} - > - - setSearchValue(value)} - placeholder="Search language..." - /> - No language found. + return ( + + + + + setSearchValue("")}> + + setSearchValue(value)} + placeholder="Search language..." + /> + No language found. - - - {items.map((language) => ( - { - editor.tf.setNodes( - { lang: value }, - { at: element } - ); - setSearchValue(value); - setOpen(false); - }} - > - - {language.label} - - ))} - - - - - - ); + + + {items.map((language) => ( + { + editor.tf.setNodes({ lang: value }, { at: element }); + setSearchValue(value); + setOpen(false); + }} + > + + {language.label} + + ))} + + + + + + ); } function CopyButton({ - value, - ...props -}: { value: (() => string) | string } & Omit< - React.ComponentProps, - 'value' ->) { - const [hasCopied, setHasCopied] = React.useState(false); + value, + ...props +}: { value: (() => string) | string } & Omit, "value">) { + const [hasCopied, setHasCopied] = React.useState(false); - React.useEffect(() => { - setTimeout(() => { - setHasCopied(false); - }, 2000); - }, [hasCopied]); + React.useEffect(() => { + setTimeout(() => { + setHasCopied(false); + }, 2000); + }, [hasCopied]); - return ( - - ); + return ( + + ); } export function CodeLineElement(props: PlateElementProps) { - return ; + return ; } export function CodeSyntaxLeaf(props: PlateLeafProps) { - const tokenClassName = props.leaf.className as string; + const tokenClassName = props.leaf.className as string; - return ; + return ; } const languages: { label: string; value: string }[] = [ - { label: 'Auto', value: 'auto' }, - { label: 'Plain Text', value: 'plaintext' }, - { label: 'ABAP', value: 'abap' }, - { label: 'Agda', value: 'agda' }, - { label: 'Arduino', value: 'arduino' }, - { label: 'ASCII Art', value: 'ascii' }, - { label: 'Assembly', value: 'x86asm' }, - { label: 'Bash', value: 'bash' }, - { label: 'BASIC', value: 'basic' }, - { label: 'BNF', value: 'bnf' }, - { label: 'C', value: 'c' }, - { label: 'C#', value: 'csharp' }, - { label: 'C++', value: 'cpp' }, - { label: 'Clojure', value: 'clojure' }, - { label: 'CoffeeScript', value: 'coffeescript' }, - { label: 'Coq', value: 'coq' }, - { label: 'CSS', value: 'css' }, - { label: 'Dart', value: 'dart' }, - { label: 'Dhall', value: 'dhall' }, - { label: 'Diff', value: 'diff' }, - { label: 'Docker', value: 'dockerfile' }, - { label: 'EBNF', value: 'ebnf' }, - { label: 'Elixir', value: 'elixir' }, - { label: 'Elm', value: 'elm' }, - { label: 'Erlang', value: 'erlang' }, - { label: 'F#', value: 'fsharp' }, - { label: 'Flow', value: 'flow' }, - { label: 'Fortran', value: 'fortran' }, - { label: 'Gherkin', value: 'gherkin' }, - { label: 'GLSL', value: 'glsl' }, - { label: 'Go', value: 'go' }, - { label: 'GraphQL', value: 'graphql' }, - { label: 'Groovy', value: 'groovy' }, - { label: 'Haskell', value: 'haskell' }, - { label: 'HCL', value: 'hcl' }, - { label: 'HTML', value: 'html' }, - { label: 'Idris', value: 'idris' }, - { label: 'Java', value: 'java' }, - { label: 'JavaScript', value: 'javascript' }, - { label: 'JSON', value: 'json' }, - { label: 'Julia', value: 'julia' }, - { label: 'Kotlin', value: 'kotlin' }, - { label: 'LaTeX', value: 'latex' }, - { label: 'Less', value: 'less' }, - { label: 'Lisp', value: 'lisp' }, - { label: 'LiveScript', value: 'livescript' }, - { label: 'LLVM IR', value: 'llvm' }, - { label: 'Lua', value: 'lua' }, - { label: 'Makefile', value: 'makefile' }, - { label: 'Markdown', value: 'markdown' }, - { label: 'Markup', value: 'markup' }, - { label: 'MATLAB', value: 'matlab' }, - { label: 'Mathematica', value: 'mathematica' }, - { label: 'Mermaid', value: 'mermaid' }, - { label: 'Nix', value: 'nix' }, - { label: 'Notion Formula', value: 'notion' }, - { label: 'Objective-C', value: 'objectivec' }, - { label: 'OCaml', value: 'ocaml' }, - { label: 'Pascal', value: 'pascal' }, - { label: 'Perl', value: 'perl' }, - { label: 'PHP', value: 'php' }, - { label: 'PowerShell', value: 'powershell' }, - { label: 'Prolog', value: 'prolog' }, - { label: 'Protocol Buffers', value: 'protobuf' }, - { label: 'PureScript', value: 'purescript' }, - { label: 'Python', value: 'python' }, - { label: 'R', value: 'r' }, - { label: 'Racket', value: 'racket' }, - { label: 'Reason', value: 'reasonml' }, - { label: 'Ruby', value: 'ruby' }, - { label: 'Rust', value: 'rust' }, - { label: 'Sass', value: 'scss' }, - { label: 'Scala', value: 'scala' }, - { label: 'Scheme', value: 'scheme' }, - { label: 'SCSS', value: 'scss' }, - { label: 'Shell', value: 'shell' }, - { label: 'Smalltalk', value: 'smalltalk' }, - { label: 'Solidity', value: 'solidity' }, - { label: 'SQL', value: 'sql' }, - { label: 'Swift', value: 'swift' }, - { label: 'TOML', value: 'toml' }, - { label: 'TypeScript', value: 'typescript' }, - { label: 'VB.Net', value: 'vbnet' }, - { label: 'Verilog', value: 'verilog' }, - { label: 'VHDL', value: 'vhdl' }, - { label: 'Visual Basic', value: 'vbnet' }, - { label: 'WebAssembly', value: 'wasm' }, - { label: 'XML', value: 'xml' }, - { label: 'YAML', value: 'yaml' }, + { label: "Auto", value: "auto" }, + { label: "Plain Text", value: "plaintext" }, + { label: "ABAP", value: "abap" }, + { label: "Agda", value: "agda" }, + { label: "Arduino", value: "arduino" }, + { label: "ASCII Art", value: "ascii" }, + { label: "Assembly", value: "x86asm" }, + { label: "Bash", value: "bash" }, + { label: "BASIC", value: "basic" }, + { label: "BNF", value: "bnf" }, + { label: "C", value: "c" }, + { label: "C#", value: "csharp" }, + { label: "C++", value: "cpp" }, + { label: "Clojure", value: "clojure" }, + { label: "CoffeeScript", value: "coffeescript" }, + { label: "Coq", value: "coq" }, + { label: "CSS", value: "css" }, + { label: "Dart", value: "dart" }, + { label: "Dhall", value: "dhall" }, + { label: "Diff", value: "diff" }, + { label: "Docker", value: "dockerfile" }, + { label: "EBNF", value: "ebnf" }, + { label: "Elixir", value: "elixir" }, + { label: "Elm", value: "elm" }, + { label: "Erlang", value: "erlang" }, + { label: "F#", value: "fsharp" }, + { label: "Flow", value: "flow" }, + { label: "Fortran", value: "fortran" }, + { label: "Gherkin", value: "gherkin" }, + { label: "GLSL", value: "glsl" }, + { label: "Go", value: "go" }, + { label: "GraphQL", value: "graphql" }, + { label: "Groovy", value: "groovy" }, + { label: "Haskell", value: "haskell" }, + { label: "HCL", value: "hcl" }, + { label: "HTML", value: "html" }, + { label: "Idris", value: "idris" }, + { label: "Java", value: "java" }, + { label: "JavaScript", value: "javascript" }, + { label: "JSON", value: "json" }, + { label: "Julia", value: "julia" }, + { label: "Kotlin", value: "kotlin" }, + { label: "LaTeX", value: "latex" }, + { label: "Less", value: "less" }, + { label: "Lisp", value: "lisp" }, + { label: "LiveScript", value: "livescript" }, + { label: "LLVM IR", value: "llvm" }, + { label: "Lua", value: "lua" }, + { label: "Makefile", value: "makefile" }, + { label: "Markdown", value: "markdown" }, + { label: "Markup", value: "markup" }, + { label: "MATLAB", value: "matlab" }, + { label: "Mathematica", value: "mathematica" }, + { label: "Mermaid", value: "mermaid" }, + { label: "Nix", value: "nix" }, + { label: "Notion Formula", value: "notion" }, + { label: "Objective-C", value: "objectivec" }, + { label: "OCaml", value: "ocaml" }, + { label: "Pascal", value: "pascal" }, + { label: "Perl", value: "perl" }, + { label: "PHP", value: "php" }, + { label: "PowerShell", value: "powershell" }, + { label: "Prolog", value: "prolog" }, + { label: "Protocol Buffers", value: "protobuf" }, + { label: "PureScript", value: "purescript" }, + { label: "Python", value: "python" }, + { label: "R", value: "r" }, + { label: "Racket", value: "racket" }, + { label: "Reason", value: "reasonml" }, + { label: "Ruby", value: "ruby" }, + { label: "Rust", value: "rust" }, + { label: "Sass", value: "scss" }, + { label: "Scala", value: "scala" }, + { label: "Scheme", value: "scheme" }, + { label: "SCSS", value: "scss" }, + { label: "Shell", value: "shell" }, + { label: "Smalltalk", value: "smalltalk" }, + { label: "Solidity", value: "solidity" }, + { label: "SQL", value: "sql" }, + { label: "Swift", value: "swift" }, + { label: "TOML", value: "toml" }, + { label: "TypeScript", value: "typescript" }, + { label: "VB.Net", value: "vbnet" }, + { label: "Verilog", value: "verilog" }, + { label: "VHDL", value: "vhdl" }, + { label: "Visual Basic", value: "vbnet" }, + { label: "WebAssembly", value: "wasm" }, + { label: "XML", value: "xml" }, + { label: "YAML", value: "yaml" }, ]; diff --git a/surfsense_web/components/ui/code-node.tsx b/surfsense_web/components/ui/code-node.tsx index 5295b9c83..d68cbd4ec 100644 --- a/surfsense_web/components/ui/code-node.tsx +++ b/surfsense_web/components/ui/code-node.tsx @@ -1,19 +1,19 @@ -'use client'; +"use client"; -import * as React from 'react'; +import * as React from "react"; -import type { PlateLeafProps } from 'platejs/react'; +import type { PlateLeafProps } from "platejs/react"; -import { PlateLeaf } from 'platejs/react'; +import { PlateLeaf } from "platejs/react"; export function CodeLeaf(props: PlateLeafProps) { - return ( - - {props.children} - - ); + return ( + + {props.children} + + ); } diff --git a/surfsense_web/components/ui/dropdown-menu.tsx b/surfsense_web/components/ui/dropdown-menu.tsx index 015dc5f5d..723a9466e 100644 --- a/surfsense_web/components/ui/dropdown-menu.tsx +++ b/surfsense_web/components/ui/dropdown-menu.tsx @@ -1,257 +1,228 @@ -"use client" +"use client"; -import * as React from "react" -import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" -import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui" +import * as React from "react"; +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"; +import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -function DropdownMenu({ - ...props -}: React.ComponentProps) { - return +function DropdownMenu({ ...props }: React.ComponentProps) { + return ; } function DropdownMenuPortal({ - ...props + ...props }: React.ComponentProps) { - return ( - - ) + return ; } function DropdownMenuTrigger({ - ...props + ...props }: React.ComponentProps) { - return ( - - ) + return ; } function DropdownMenuContent({ - className, - sideOffset = 4, - ...props + className, + sideOffset = 4, + ...props }: React.ComponentProps) { - return ( - - - - ) + return ( + + + + ); } -function DropdownMenuGroup({ - ...props -}: React.ComponentProps) { - return ( - - ) +function DropdownMenuGroup({ ...props }: React.ComponentProps) { + return ; } function DropdownMenuItem({ - className, - inset, - variant = "default", - ...props + className, + inset, + variant = "default", + ...props }: React.ComponentProps & { - inset?: boolean - variant?: "default" | "destructive" + inset?: boolean; + variant?: "default" | "destructive"; }) { - return ( - - ) + return ( + + ); } function DropdownMenuCheckboxItem({ - className, - children, - checked, - ...props + className, + children, + checked, + ...props }: React.ComponentProps) { - return ( - - - - - - - {children} - - ) + return ( + + + + + + + {children} + + ); } function DropdownMenuRadioGroup({ - ...props + ...props }: React.ComponentProps) { - return ( - - ) + return ; } function DropdownMenuRadioItem({ - className, - children, - ...props + className, + children, + ...props }: React.ComponentProps) { - return ( - - - - - - - {children} - - ) + return ( + + + + + + + {children} + + ); } function DropdownMenuLabel({ - className, - inset, - ...props + className, + inset, + ...props }: React.ComponentProps & { - inset?: boolean + inset?: boolean; }) { - return ( - - ) + return ( + + ); } function DropdownMenuSeparator({ - className, - ...props + className, + ...props }: React.ComponentProps) { - return ( - - ) + return ( + + ); } -function DropdownMenuShortcut({ - className, - ...props -}: React.ComponentProps<"span">) { - return ( - - ) +function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) { + return ( + + ); } -function DropdownMenuSub({ - ...props -}: React.ComponentProps) { - return +function DropdownMenuSub({ ...props }: React.ComponentProps) { + return ; } function DropdownMenuSubTrigger({ - className, - inset, - children, - ...props + className, + inset, + children, + ...props }: React.ComponentProps & { - inset?: boolean + inset?: boolean; }) { - return ( - - {children} - - - ) + return ( + + {children} + + + ); } function DropdownMenuSubContent({ - className, - ...props + className, + ...props }: React.ComponentProps) { - return ( - - ) + return ( + + ); } export { - DropdownMenu, - DropdownMenuPortal, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuLabel, - DropdownMenuItem, - DropdownMenuCheckboxItem, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuSub, - DropdownMenuSubTrigger, - DropdownMenuSubContent, -} + DropdownMenu, + DropdownMenuPortal, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, +}; diff --git a/surfsense_web/components/ui/editor.tsx b/surfsense_web/components/ui/editor.tsx index 185327023..718704f3a 100644 --- a/surfsense_web/components/ui/editor.tsx +++ b/surfsense_web/components/ui/editor.tsx @@ -1,132 +1,124 @@ -'use client'; +"use client"; -import * as React from 'react'; +import * as React from "react"; -import type { VariantProps } from 'class-variance-authority'; -import type { PlateContentProps, PlateViewProps } from 'platejs/react'; +import type { VariantProps } from "class-variance-authority"; +import type { PlateContentProps, PlateViewProps } from "platejs/react"; -import { cva } from 'class-variance-authority'; -import { PlateContainer, PlateContent, PlateView } from 'platejs/react'; +import { cva } from "class-variance-authority"; +import { PlateContainer, PlateContent, PlateView } from "platejs/react"; -import { cn } from '@/lib/utils'; +import { cn } from "@/lib/utils"; const editorContainerVariants = cva( - 'relative w-full cursor-text select-text overflow-y-auto caret-primary selection:bg-brand/25 focus-visible:outline-none [&_.slate-selection-area]:z-50 [&_.slate-selection-area]:border [&_.slate-selection-area]:border-brand/25 [&_.slate-selection-area]:bg-brand/15', - { - defaultVariants: { - variant: 'default', - }, - variants: { - variant: { - comment: cn( - 'flex flex-wrap justify-between gap-1 px-1 py-0.5 text-sm', - 'rounded-md border-[1.5px] border-transparent bg-transparent', - 'has-[[data-slate-editor]:focus]:border-brand/50 has-[[data-slate-editor]:focus]:ring-2 has-[[data-slate-editor]:focus]:ring-brand/30', - 'has-aria-disabled:border-input has-aria-disabled:bg-muted' - ), - default: 'h-full', - demo: 'h-[650px]', - select: cn( - 'group rounded-md border border-input ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2', - 'has-data-readonly:w-fit has-data-readonly:cursor-default has-data-readonly:border-transparent has-data-readonly:focus-within:[box-shadow:none]' - ), - }, - }, - } + "relative w-full cursor-text select-text overflow-y-auto caret-primary selection:bg-brand/25 focus-visible:outline-none [&_.slate-selection-area]:z-50 [&_.slate-selection-area]:border [&_.slate-selection-area]:border-brand/25 [&_.slate-selection-area]:bg-brand/15", + { + defaultVariants: { + variant: "default", + }, + variants: { + variant: { + comment: cn( + "flex flex-wrap justify-between gap-1 px-1 py-0.5 text-sm", + "rounded-md border-[1.5px] border-transparent bg-transparent", + "has-[[data-slate-editor]:focus]:border-brand/50 has-[[data-slate-editor]:focus]:ring-2 has-[[data-slate-editor]:focus]:ring-brand/30", + "has-aria-disabled:border-input has-aria-disabled:bg-muted" + ), + default: "h-full", + demo: "h-[650px]", + select: cn( + "group rounded-md border border-input ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2", + "has-data-readonly:w-fit has-data-readonly:cursor-default has-data-readonly:border-transparent has-data-readonly:focus-within:[box-shadow:none]" + ), + }, + }, + } ); export function EditorContainer({ - className, - variant, - ...props -}: React.ComponentProps<'div'> & VariantProps) { - return ( - - ); + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( + + ); } const editorVariants = cva( - cn( - 'group/editor', - 'relative w-full cursor-text select-text overflow-x-hidden whitespace-pre-wrap break-words', - 'rounded-md ring-offset-background focus-visible:outline-none', - '**:data-slate-placeholder:!top-1/2 **:data-slate-placeholder:-translate-y-1/2 placeholder:text-muted-foreground/80 **:data-slate-placeholder:text-muted-foreground/80 **:data-slate-placeholder:opacity-100!', - '[&_strong]:font-bold' - ), - { - defaultVariants: { - variant: 'default', - }, - variants: { - disabled: { - true: 'cursor-not-allowed opacity-50', - }, - focused: { - true: 'ring-2 ring-ring ring-offset-2', - }, - variant: { - ai: 'w-full px-0 text-base md:text-sm', - aiChat: - 'max-h-[min(70vh,320px)] w-full overflow-y-auto px-3 py-2 text-base md:text-sm', - comment: cn('rounded-none border-none bg-transparent text-sm'), - default: - 'size-full px-6 pt-4 pb-72 text-base sm:px-[max(64px,calc(50%-350px))]', - demo: 'size-full px-6 pt-4 pb-72 text-base sm:px-[max(64px,calc(50%-350px))]', - fullWidth: 'size-full px-6 pt-4 pb-72 text-base sm:px-24', - none: '', - select: 'px-3 py-2 text-base data-readonly:w-fit', - }, - }, - } + cn( + "group/editor", + "relative w-full cursor-text select-text overflow-x-hidden whitespace-pre-wrap break-words", + "rounded-md ring-offset-background focus-visible:outline-none", + "**:data-slate-placeholder:!top-1/2 **:data-slate-placeholder:-translate-y-1/2 placeholder:text-muted-foreground/80 **:data-slate-placeholder:text-muted-foreground/80 **:data-slate-placeholder:opacity-100!", + "[&_strong]:font-bold" + ), + { + defaultVariants: { + variant: "default", + }, + variants: { + disabled: { + true: "cursor-not-allowed opacity-50", + }, + focused: { + true: "ring-2 ring-ring ring-offset-2", + }, + variant: { + ai: "w-full px-0 text-base md:text-sm", + aiChat: "max-h-[min(70vh,320px)] w-full overflow-y-auto px-3 py-2 text-base md:text-sm", + comment: cn("rounded-none border-none bg-transparent text-sm"), + default: "size-full px-6 pt-4 pb-72 text-base sm:px-[max(64px,calc(50%-350px))]", + demo: "size-full px-6 pt-4 pb-72 text-base sm:px-[max(64px,calc(50%-350px))]", + fullWidth: "size-full px-6 pt-4 pb-72 text-base sm:px-24", + none: "", + select: "px-3 py-2 text-base data-readonly:w-fit", + }, + }, + } ); -export type EditorProps = PlateContentProps & - VariantProps; +export type EditorProps = PlateContentProps & VariantProps; export const Editor = ({ - className, - disabled, - focused, - variant, - ref, - ...props + className, + disabled, + focused, + variant, + ref, + ...props }: EditorProps & { ref?: React.RefObject }) => ( - + ); -Editor.displayName = 'Editor'; +Editor.displayName = "Editor"; export function EditorView({ - className, - variant, - ...props + className, + variant, + ...props }: PlateViewProps & VariantProps) { - return ( - - ); + return ; } -EditorView.displayName = 'EditorView'; +EditorView.displayName = "EditorView"; diff --git a/surfsense_web/components/ui/equation-node.tsx b/surfsense_web/components/ui/equation-node.tsx index 66b3bb3d7..34c3ac329 100644 --- a/surfsense_web/components/ui/equation-node.tsx +++ b/surfsense_web/components/ui/equation-node.tsx @@ -1,183 +1,176 @@ -'use client'; +"use client"; -import * as React from 'react'; +import * as React from "react"; -import type { TEquationElement } from 'platejs'; +import type { TEquationElement } from "platejs"; -import { - useEquationElement, - useEquationInput, -} from '@platejs/math/react'; -import { RadicalIcon } from 'lucide-react'; -import { type PlateElementProps, PlateElement, useSelected } from 'platejs/react'; +import { useEquationElement, useEquationInput } from "@platejs/math/react"; +import { RadicalIcon } from "lucide-react"; +import { type PlateElementProps, PlateElement, useSelected } from "platejs/react"; -import { cn } from '@/lib/utils'; +import { cn } from "@/lib/utils"; -export function EquationElement({ - children, - ...props -}: PlateElementProps) { - const element = props.element; - const selected = useSelected(); - const katexRef = React.useRef(null); - const [isEditing, setIsEditing] = React.useState(false); +export function EquationElement({ children, ...props }: PlateElementProps) { + const element = props.element; + const selected = useSelected(); + const katexRef = React.useRef(null); + const [isEditing, setIsEditing] = React.useState(false); - useEquationElement({ - element, - katexRef, - options: { - displayMode: true, - throwOnError: false, - }, - }); + useEquationElement({ + element, + katexRef, + options: { + displayMode: true, + throwOnError: false, + }, + }); - const { props: inputProps, ref: inputRef, onDismiss, onSubmit } = useEquationInput({ - isInline: false, - open: isEditing, - onClose: () => setIsEditing(false), - }); + const { + props: inputProps, + ref: inputRef, + onDismiss, + onSubmit, + } = useEquationInput({ + isInline: false, + open: isEditing, + onClose: () => setIsEditing(false), + }); - return ( - -
    setIsEditing(true)} - > - {element.texExpression ? ( -
    - ) : ( -
    - - Add an equation -
    - )} -
    + return ( + +
    setIsEditing(true)} + > + {element.texExpression ? ( +
    + ) : ( +
    + + Add an equation +
    + )} +
    - {isEditing && ( -
    -