@ecsplico/editor 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +4 -3
- package/dist/adapters/anchors.d.ts +80 -0
- package/dist/adapters/anchors.js +270 -0
- package/dist/adapters/anchors.js.map +1 -0
- package/dist/adapters/comments/client.d.ts +64 -0
- package/dist/adapters/comments/client.js +536 -0
- package/dist/adapters/comments/client.js.map +1 -0
- package/dist/adapters/comments/drizzle.d.ts +430 -0
- package/dist/adapters/comments/drizzle.js +236 -0
- package/dist/adapters/comments/drizzle.js.map +1 -0
- package/dist/adapters/comments/errors.d.ts +9 -0
- package/dist/adapters/comments/errors.js +19 -0
- package/dist/adapters/comments/errors.js.map +1 -0
- package/dist/adapters/comments/json.d.ts +19 -0
- package/dist/adapters/comments/json.js +94 -0
- package/dist/adapters/comments/json.js.map +1 -0
- package/dist/adapters/comments/record.js +78 -0
- package/dist/adapters/comments/record.js.map +1 -0
- package/dist/adapters/comments/server.d.ts +23 -0
- package/dist/adapters/comments/server.js +102 -0
- package/dist/adapters/comments/server.js.map +1 -0
- package/dist/adapters/comments/types.d.ts +109 -0
- package/dist/adapters/documents.js +108 -0
- package/dist/adapters/documents.js.map +1 -0
- package/dist/adapters/drizzle.d.ts +293 -0
- package/dist/adapters/drizzle.js +157 -0
- package/dist/adapters/drizzle.js.map +1 -0
- package/dist/adapters/files.js +30 -0
- package/dist/adapters/files.js.map +1 -0
- package/dist/adapters/forge.d.ts +58 -0
- package/dist/adapters/forge.js +91 -0
- package/dist/adapters/forge.js.map +1 -0
- package/dist/adapters/index.d.ts +13 -0
- package/dist/adapters/index.js +0 -0
- package/dist/adapters/json.d.ts +24 -0
- package/dist/adapters/json.js +128 -0
- package/dist/adapters/json.js.map +1 -0
- package/dist/adapters/nitro.d.ts +61 -0
- package/dist/adapters/nitro.js +212 -0
- package/dist/adapters/nitro.js.map +1 -0
- package/dist/adapters/routes.js +16 -0
- package/dist/adapters/routes.js.map +1 -0
- package/dist/adapters/shared.d.ts +37 -0
- package/dist/adapters/shared.js +32 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/vue.d.ts +67 -0
- package/dist/adapters/vue.js +187 -0
- package/dist/adapters/vue.js.map +1 -0
- package/dist/code/CodeSurface.vue.d.ts +3 -3
- package/dist/core/doc-anchors.d.ts +5 -0
- package/dist/core/doc-anchors.js +4 -3
- package/dist/core/doc-anchors.js.map +1 -1
- package/dist/core/editor-ui.js +57 -1
- package/dist/core/editor-ui.js.map +1 -1
- package/dist/core/surface-session.js +139 -0
- package/dist/core/surface-session.js.map +1 -0
- package/dist/document/DocumentSurface.d.ts +1 -1
- package/dist/document/DocumentSurface.vue.d.ts +3 -3
- package/dist/features/ai/agent-op-review.d.ts +2 -2
- package/dist/features/ai/agent-op-review.js.map +1 -1
- package/dist/features/ai/agent-ops.d.ts +1 -1
- package/dist/features/ai/agent-ops.js +9 -6
- package/dist/features/ai/agent-ops.js.map +1 -1
- package/dist/features/ai/ai-apply.d.ts +1 -1
- package/dist/features/ai/ai-review.js +10 -1
- package/dist/features/ai/ai-review.js.map +1 -1
- package/dist/features/ai/components/AiSelectionMenu.js +78 -252
- package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
- package/dist/features/ai/components/AiSelectionMenu.vue.d.ts +2 -1
- package/dist/features/ai/components/AiSidebar.js +2 -2
- package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/components/DocumentChatOps.js +1 -0
- package/dist/features/ai/components/DocumentChatOps.js.map +1 -1
- package/dist/features/ai/components/ModelQuickSwitch.js +1 -1
- package/dist/features/ai/components/SettingsAiTab.js +1 -1
- package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/document-chat.d.ts +3 -2
- package/dist/features/ai/document-chat.js +16 -19
- package/dist/features/ai/document-chat.js.map +1 -1
- package/dist/features/ai/index.d.ts +5 -3
- package/dist/features/ai/index.js +4 -2
- package/dist/features/ai/protocol/agent-ops.d.ts +22 -17
- package/dist/features/ai/protocol/agent-ops.js +65 -39
- package/dist/features/ai/protocol/agent-ops.js.map +1 -1
- package/dist/features/ai/protocol/block-lines.d.ts +89 -0
- package/dist/features/ai/protocol/block-lines.js +177 -0
- package/dist/features/ai/protocol/block-lines.js.map +1 -0
- package/dist/features/ai/protocol/chat.js +10 -10
- package/dist/features/ai/protocol/chat.js.map +1 -1
- package/dist/features/ai/protocol/index.d.ts +3 -2
- package/dist/features/ai/protocol/index.js +3 -2
- package/dist/features/ai/react/AiSelectionMenu.d.ts +3 -2
- package/dist/features/ai/react/AiSelectionMenu.js +64 -255
- package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
- package/dist/features/ai/react/AiSidebar.js +2 -2
- package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/react/DocumentChatOps.js +1 -0
- package/dist/features/ai/react/DocumentChatOps.js.map +1 -1
- package/dist/features/ai/react/ModelQuickSwitch.js +1 -1
- package/dist/features/ai/react/SettingsAiTab.js +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/react/use-ai-selection-flow.js +34 -0
- package/dist/features/ai/react/use-ai-selection-flow.js.map +1 -0
- package/dist/features/ai/selection-flow.d.ts +133 -0
- package/dist/features/ai/selection-flow.js +283 -0
- package/dist/features/ai/selection-flow.js.map +1 -0
- package/dist/features/ai/use-ai-assistant.d.ts +1 -1
- package/dist/features/ai/use-document-chat.d.ts +1 -1
- package/dist/features/authors/components/AuthorColorsPanel.js +1 -1
- package/dist/features/authors/feature.js +1 -1
- package/dist/features/authors/index.js +1 -1
- package/dist/features/authors/react/AuthorColorsPanel.js +1 -1
- package/dist/features/comments/components/CommentBubble.js +1 -1
- package/dist/features/comments/components/CommentComposer.js +1 -1
- package/dist/features/comments/components/CommentThreadView.js +5 -2
- package/dist/features/comments/components/CommentThreadView.js.map +1 -1
- package/dist/features/comments/components/CommentsMargin.js +108 -230
- package/dist/features/comments/components/CommentsMargin.js.map +1 -1
- package/dist/features/comments/components/CommentsOverlay.js +1 -1
- package/dist/features/comments/feature.js +1 -1
- package/dist/features/comments/index.d.ts +4 -2
- package/dist/features/comments/index.js +4 -2
- package/dist/features/comments/margin-controller.d.ts +44 -0
- package/dist/features/comments/margin-controller.js +132 -0
- package/dist/features/comments/margin-controller.js.map +1 -0
- package/dist/features/comments/margin-layout.d.ts +55 -0
- package/dist/features/comments/margin-layout.js +79 -0
- package/dist/features/comments/margin-layout.js.map +1 -0
- package/dist/features/comments/model.d.ts +8 -0
- package/dist/features/comments/model.js +60 -34
- package/dist/features/comments/model.js.map +1 -1
- package/dist/features/comments/react/CommentBubble.js +1 -1
- package/dist/features/comments/react/CommentComposer.js +1 -1
- package/dist/features/comments/react/CommentThreadView.js +2 -2
- package/dist/features/comments/react/CommentThreadView.js.map +1 -1
- package/dist/features/comments/react/CommentsMargin.d.ts +0 -5
- package/dist/features/comments/react/CommentsMargin.js +85 -251
- package/dist/features/comments/react/CommentsMargin.js.map +1 -1
- package/dist/features/comments/react/CommentsOverlay.js +1 -1
- package/dist/features/comments/react/use-comments.js +1 -1
- package/dist/features/comments/react/use-margin-layout.js +45 -0
- package/dist/features/comments/react/use-margin-layout.js.map +1 -0
- package/dist/features/comments/react.d.ts +1 -1
- package/dist/features/comments/schema.d.ts +23 -0
- package/dist/features/comments/schema.js.map +1 -1
- package/dist/features/comments/snapshot-equality.js +40 -0
- package/dist/features/comments/snapshot-equality.js.map +1 -0
- package/dist/features/comments/thread-anchor.js +33 -0
- package/dist/features/comments/thread-anchor.js.map +1 -0
- package/dist/features/comments/use-comments.d.ts +3 -1
- package/dist/features/comments/use-comments.js +1 -0
- package/dist/features/comments/use-comments.js.map +1 -1
- package/dist/features/comments/use-margin-layout.js +29 -0
- package/dist/features/comments/use-margin-layout.js.map +1 -0
- package/dist/features/comments/vue.d.ts +1 -1
- package/dist/features/composites/accepted-children.d.ts +21 -0
- package/dist/features/composites/accepted-children.js +126 -0
- package/dist/features/composites/accepted-children.js.map +1 -0
- package/dist/features/composites/components/CompositeDetails.js +1 -1
- package/dist/features/composites/components/CompositeInspector.js +38 -164
- package/dist/features/composites/components/CompositeInspector.js.map +1 -1
- package/dist/features/composites/feature.d.ts +2 -1
- package/dist/features/composites/feature.js +8 -2
- package/dist/features/composites/feature.js.map +1 -1
- package/dist/features/composites/index.d.ts +5 -3
- package/dist/features/composites/index.js +8 -6
- package/dist/features/composites/inspector-model.d.ts +97 -0
- package/dist/features/composites/inspector-model.js +255 -0
- package/dist/features/composites/inspector-model.js.map +1 -0
- package/dist/features/composites/kinds.d.ts +11 -0
- package/dist/features/composites/kinds.js +17 -2
- package/dist/features/composites/kinds.js.map +1 -1
- package/dist/features/composites/outline.d.ts +18 -5
- package/dist/features/composites/outline.js +44 -12
- package/dist/features/composites/outline.js.map +1 -1
- package/dist/features/composites/react/CompositeDetails.js +1 -1
- package/dist/features/composites/react/CompositeInspector.d.ts +1 -1
- package/dist/features/composites/react/CompositeInspector.js +21 -180
- package/dist/features/composites/react/CompositeInspector.js.map +1 -1
- package/dist/features/composites/react/CompositeStatusMenu.d.ts +1 -1
- package/dist/features/composites/react/CompositeView.js +1 -1
- package/dist/features/composites/react/use-composite-inspector.js +29 -0
- package/dist/features/composites/react/use-composite-inspector.js.map +1 -0
- package/dist/features/composites/schema.d.ts +2 -2
- package/dist/features/composites/schema.js +2 -2
- package/dist/features/composites/schema.js.map +1 -1
- package/dist/features/composites/use-composite-anchors.d.ts +1 -1
- package/dist/features/composites/use-composite-inspector.js +41 -0
- package/dist/features/composites/use-composite-inspector.js.map +1 -0
- package/dist/features/composites/use-composite-rows.d.ts +1 -1
- package/dist/features/media/index.d.ts +1 -1
- package/dist/features/media/vue.d.ts +1 -1
- package/dist/features/mentions/index.d.ts +3 -3
- package/dist/features/mentions/index.js +3 -3
- package/dist/features/mentions/schema.d.ts +1 -1
- package/dist/features/mentions/schema.js +1 -1
- package/dist/features/themes/index.d.ts +1 -1
- package/dist/features/themes/index.js +1 -1
- package/dist/features/transcript/feature.js +1 -1
- package/dist/features/transcript/index.js +2 -2
- package/dist/i18n/de/ai.js +2 -0
- package/dist/i18n/de/ai.js.map +1 -1
- package/dist/i18n/de/composites.js +8 -1
- package/dist/i18n/de/composites.js.map +1 -1
- package/dist/i18n/de/tree.js +14 -1
- package/dist/i18n/de/tree.js.map +1 -1
- package/dist/i18n/de.d.ts +22 -0
- package/dist/i18n/en/ai.js +2 -0
- package/dist/i18n/en/ai.js.map +1 -1
- package/dist/i18n/en/composites.js +8 -1
- package/dist/i18n/en/composites.js.map +1 -1
- package/dist/i18n/en/tree.js +14 -1
- package/dist/i18n/en/tree.js.map +1 -1
- package/dist/integrations/composite-ai/composite-fill.js +2 -2
- package/dist/integrations/composite-ai/composite-summary.js +2 -2
- package/dist/integrations/composite-ai/index.js +2 -2
- package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
- package/dist/markdown/MarkdownSurface.d.ts +1 -1
- package/dist/markdown/MarkdownSurface.js +2 -2
- package/dist/markdown/MarkdownSurface.vue.d.ts +7 -7
- package/dist/markdown/index.d.ts +2 -2
- package/dist/nuxt/runtime/server/routes/collab/[id].js +1 -1
- package/dist/react/EditorSurface.d.ts +1 -1
- package/dist/react/EditorSurface.js +43 -149
- package/dist/react/EditorSurface.js.map +1 -1
- package/dist/react/EditorToolbar.js +3 -40
- package/dist/react/EditorToolbar.js.map +1 -1
- package/dist/schema/advanced.js +2 -2
- package/dist/server/adapter.d.ts +2 -2
- package/dist/server/collab.js +1 -1
- package/dist/server/handlers/ai.d.ts +4 -2
- package/dist/server/handlers/ai.js +11 -9
- package/dist/server/handlers/ai.js.map +1 -1
- package/dist/server/handlers/context.d.ts +1 -1
- package/dist/server/handlers/editor.d.ts +1 -1
- package/dist/server/handlers/editor.js +2 -2
- package/dist/server/host.d.ts +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/settings/index.d.ts +1 -1
- package/dist/styles/tokens.css +1 -1
- package/dist/tree/DocumentTree.js +159 -387
- package/dist/tree/DocumentTree.js.map +1 -1
- package/dist/tree/drop-rules.js +37 -0
- package/dist/tree/drop-rules.js.map +1 -0
- package/dist/tree/react/DocumentTree.d.ts +2 -2
- package/dist/tree/react/DocumentTree.js +135 -397
- package/dist/tree/react/DocumentTree.js.map +1 -1
- package/dist/tree/react/use-tree-model.js +129 -0
- package/dist/tree/react/use-tree-model.js.map +1 -0
- package/dist/tree/react.js +1 -1
- package/dist/tree/tree-host.js +100 -0
- package/dist/tree/tree-host.js.map +1 -0
- package/dist/tree/tree-items.js +22 -43
- package/dist/tree/tree-items.js.map +1 -1
- package/dist/tree/tree-model.js +580 -0
- package/dist/tree/tree-model.js.map +1 -0
- package/dist/tree/tree-rows.js +77 -0
- package/dist/tree/tree-rows.js.map +1 -0
- package/dist/tree/use-tree-model.js +94 -0
- package/dist/tree/use-tree-model.js.map +1 -0
- package/dist/tree/vue.js +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
- package/dist/vue/SimpleEditor.vue.d.ts +2 -2
- package/dist/vue/components/EditorSurface.js +27 -87
- package/dist/vue/components/EditorSurface.js.map +1 -1
- package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
- package/dist/vue/components/EditorToolbar.js +7 -44
- package/dist/vue/components/EditorToolbar.js.map +1 -1
- package/dist/vue/context.d.ts +1 -1
- package/dist/vue/context.js +1 -1
- package/dist/vue/context.js.map +1 -1
- package/dist/vue/use-editor-ui.d.ts +1 -1
- package/dist/vue/use-notifier.d.ts +1 -1
- package/docs/integration.md +97 -4
- package/docs/llms.md +9 -1
- package/docs/usage.md +10 -5
- package/package.json +67 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocumentTree.js","names":["buildOutlineItems","isDraggable","isDraggableItem"],"sources":["../../../src/tree/react/DocumentTree.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { type DragEvent, type KeyboardEvent, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { toast } from 'sonner'\nimport { compositeJSON, type CompositeKind, compositeKindsOf, moveBlockInto } from '../../features/composites'\nimport { pickFiles } from '../../features/media'\nimport { useFileImport } from '../../features/media/react'\nimport { revealBlockInEditor } from '../../outline'\nimport { Icon } from '../../react/Icon'\nimport { useEditorSettings } from '../../react/settings'\nimport { useEditorI18n } from '../../react/use-editor-i18n'\nimport { useNotifier } from '../../react/use-notifier'\nimport { useStore } from '../../react/use-store'\nimport { Button } from '../../react/ui/button'\nimport { Collapsible, CollapsibleContent } from '../../react/ui/collapsible'\nimport { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '../../react/ui/dropdown-menu'\nimport { Input } from '../../react/ui/input'\nimport { cn } from '../../react/ui/utils'\nimport type { DocumentStore } from '../store'\nimport {\n ancestorKeys,\n applyNodeFilters,\n canDrop,\n collectFolderPaths,\n collectKeys,\n compositeInsertPos,\n DRAG_MIME,\n type DragPayload,\n dragPayloadOf,\n EMPTY_OUTLINE,\n filterKinds,\n folderItem,\n folderNamesIn,\n holdsComposites,\n isDraggable as isDraggableItem,\n joinPath,\n type Outline,\n outlineItems as buildOutlineItems,\n parentPath,\n readOutline,\n type TreeItem,\n VIRTUAL_ROOT,\n virtualRootItem,\n} from '../tree-items'\nimport { remapFolderPath } from '../folder-paths'\nimport { useTreeView } from './use-tree-view'\n\n// File browser of a document store: a „Dokumente“ root with folders and documents. Every `store` method reports its own\n// failure before it rejects, so the `.catch(() => undefined)`s below only stop the flow.\n\n/** Props of `DocumentTree`. */\nexport interface DocumentTreeProps {\n /** Where documents and folders live; the host implements it (see `DocumentStore`). */\n store: DocumentStore\n /** Editor of the open document (its outline is listed under the document); `undefined` while none is mounted. */\n editor: Editor | undefined\n /** The folder new documents, folders and host imports go to (controlled). Omit it to let the tree keep it. Reset to `''` when it is deleted or moved away. */\n selectedFolder?: string\n onSelectedFolderChange?: (folder: string) => void\n /** Host actions on a document row, rendered next to its delete button (e.g. a download link). */\n documentActions?: (document: { id: string, title: string }) => ReactNode\n className?: string\n}\n\ninterface Renaming {\n item: TreeItem\n draft: string\n}\n\ninterface CreatingFolder {\n /** Folder path the folder being named is created in. */\n parent: string\n draft: string\n}\n\nconst OUTLINE_DEBOUNCE_MS = 150\nconst EMPTY_DOCUMENTS: readonly never[] = []\nconst EMPTY_KINDS: readonly CompositeKind[] = []\nconst REVEAL_ON_HOVER = 'opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 data-[state=open]:opacity-100'\nconst isOutlineItem = (item: TreeItem) => item.type === 'composite' || item.type === 'heading'\n\n/** The file browser of a document store: folders and documents with their drag and drop, the open document's outline below it. */\nexport function DocumentTree({ store, editor, selectedFolder: controlledFolder, onSelectedFolderChange, documentActions, className }: DocumentTreeProps) {\n const { t } = useEditorI18n()\n const { nodeFilters, expanded, model } = useTreeView()\n const { settings } = useEditorSettings()\n const { importFiles } = useFileImport(editor)\n const notify = useNotifier(editor)\n const tree = useStore(store.tree)\n const documents = useStore(store.documents) ?? EMPTY_DOCUMENTS\n const activeId = useStore(store.activeId)\n const structure = settings.blocks.structure\n const virtual = settings.blocks.treeRoot === 'file' && structure === 'composite' && !!editor\n\n const treeElement = useRef<HTMLUListElement>(null)\n\n // --- selected folder (controlled or kept here) -----------------------------------------------------------------\n\n const [ownFolder, setOwnFolder] = useState('')\n const selectedFolder = controlledFolder ?? ownFolder\n const selectedFolderRef = useRef(selectedFolder)\n selectedFolderRef.current = selectedFolder\n const onSelectedFolderChangeRef = useRef(onSelectedFolderChange)\n onSelectedFolderChangeRef.current = onSelectedFolderChange\n const setSelectedFolder = useCallback((folder: string) => {\n selectedFolderRef.current = folder\n setOwnFolder(folder)\n onSelectedFolderChangeRef.current?.(folder)\n }, [])\n\n // --- view state ------------------------------------------------------------------------------------------------\n\n /** Virtual mode: block ID of the selected `folder`/`document` composite; `null` = the document itself. */\n const [virtualTarget, setVirtualTarget] = useState<string | null>(null)\n // Outline entries start open: only the ones the user closed are remembered (session only), the rest of the open state\n // is the shared `expanded` list.\n const [collapsedOutline, setCollapsedOutline] = useState<string[]>([])\n const [dropKey, setDropKey] = useState<string | null>(null)\n const dragging = useRef<DragPayload | null>(null)\n const [removing, setRemoving] = useState<string | null>(null)\n\n const [renaming, setRenamingState] = useState<Renaming | null>(null)\n const renamingRef = useRef<Renaming | null>(null)\n const setRenaming = (next: Renaming | null) => {\n renamingRef.current = next\n setRenamingState(next)\n }\n const [creatingFolder, setCreatingFolderState] = useState<CreatingFolder | null>(null)\n const creatingFolderRef = useRef<CreatingFolder | null>(null)\n const setCreatingFolder = (next: CreatingFolder | null) => {\n creatingFolderRef.current = next\n setCreatingFolderState(next)\n }\n\n // --- outline of the open document ------------------------------------------------------------------------------\n\n const kinds = useMemo(() => (editor ? compositeKindsOf(editor) : EMPTY_KINDS), [editor])\n\n // Read from the editor on demand and refreshed, debounced, when the document changes.\n const [outline, setOutline] = useState<Outline>(EMPTY_OUTLINE)\n useEffect(() => {\n setOutline(readOutline(editor, structure, t))\n if (!editor) return\n let timer: ReturnType<typeof setTimeout> | undefined\n const onUpdate = () => {\n clearTimeout(timer)\n timer = setTimeout(() => setOutline(readOutline(editor, structure, t)), OUTLINE_DEBOUNCE_MS)\n }\n editor.on('update', onUpdate)\n return () => {\n clearTimeout(timer)\n editor.off('update', onUpdate)\n }\n }, [editor, structure, t])\n\n // --- expansion -------------------------------------------------------------------------------------------------\n\n const activeFolder = documents.find(d => d.id === activeId)?.folder\n\n useEffect(() => {\n if (virtual) model.expand([VIRTUAL_ROOT])\n }, [virtual, model])\n\n // Opening a document (also by URL, or after a move) shows it: its folders open and its folder becomes the selected one.\n useEffect(() => {\n model.expand(ancestorKeys(activeFolder ?? ''))\n if (activeFolder !== undefined) setSelectedFolder(activeFolder)\n }, [activeFolder, model, setSelectedFolder])\n\n // The open document shows its outline.\n useEffect(() => {\n if (activeId) model.expand([`doc:${activeId}`])\n }, [activeId, model])\n\n // A selected folder that was deleted or moved away falls back to the root.\n useEffect(() => {\n if (!tree) return\n const paths = new Set<string>()\n collectFolderPaths(tree, paths)\n if (!paths.has(selectedFolderRef.current)) setSelectedFolder('')\n }, [tree, setSelectedFolder])\n\n /** A folder moved from `from` to `to`: its open/selected state follows it. */\n function followFolderMove(from: string, to: string) {\n model.followFolderMove(from, to)\n setSelectedFolder(remapFolderPath(selectedFolderRef.current, from, to))\n model.expand(ancestorKeys(parentPath(to)))\n }\n\n /** Makes a composite's children visible in the outline again. */\n function openOutline(blockId: string) {\n setCollapsedOutline(current => current.filter(key => key !== `outline:${blockId}`))\n }\n\n // --- items -----------------------------------------------------------------------------------------------------\n\n function selectItem(item: TreeItem) {\n if (renamingRef.current?.item.key === item.key) return\n if (item.key === VIRTUAL_ROOT) {\n setVirtualTarget(null)\n return\n }\n if (item.type === 'document') {\n setSelectedFolder(item.folder ?? '')\n void store.open(item.id!).catch(() => undefined)\n } else {\n setSelectedFolder(item.path ?? '')\n }\n }\n\n function selectOutline(item: TreeItem) {\n if (editor && !editor.isDestroyed) revealBlockInEditor(editor, { id: item.blockId ?? null, pos: item.pos! })\n if (virtual && item.blockId && (item.kind === 'folder' || item.kind === 'document')) setVirtualTarget(item.blockId)\n }\n\n const outlineList = buildOutlineItems(outline.nodes as Parameters<typeof buildOutlineItems>[0], kinds, selectOutline)\n const unfilteredItems: TreeItem[] = virtual\n ? [virtualRootItem(documents.find(d => d.id === activeId)?.title ?? t('tree.rootFallback'), outlineList, selectItem)]\n : tree ? [folderItem(tree, activeId, outlineList, selectItem)] : []\n const items = applyNodeFilters(unfilteredItems, nodeFilters)\n const isEmpty = !virtual && !!tree && !tree.folders.length && !tree.documents.length\n\n const isOpen = (item: TreeItem) => (isOutlineItem(item) ? !collapsedOutline.includes(item.key) : expanded.includes(item.key))\n\n function setOpen(item: TreeItem, open: boolean) {\n if (isOutlineItem(item)) {\n setCollapsedOutline(current => (open ? current.filter(key => key !== item.key) : current.includes(item.key) ? current : [...current, item.key]))\n } else if (open) {\n model.expand([item.key])\n } else {\n model.expanded.set(model.expanded.get().filter(key => key !== item.key))\n }\n }\n\n // Folders and the root toggle through the row click; documents and outline entries (whose row click opens or reveals)\n // toggle through their chevron.\n function onChevronClick(event: { stopPropagation: () => void }, item: TreeItem) {\n if (item.type === 'root' || item.type === 'folder') return\n event.stopPropagation()\n setOpen(item, !isOpen(item))\n }\n\n function activate(item: TreeItem) {\n item.onSelect?.()\n if (item.type === 'root' || item.type === 'folder') setOpen(item, !isOpen(item))\n }\n\n function onRowKeyDown(event: KeyboardEvent<HTMLDivElement>, item: TreeItem) {\n if (event.target !== event.currentTarget) return\n const expandable = item.type === 'root' || item.type === 'folder' || !!item.children?.length\n if (event.key === 'Enter' || event.key === ' ') activate(item)\n else if (event.key === 'ArrowRight' && expandable && !isOpen(item)) setOpen(item, true)\n else if (event.key === 'ArrowLeft' && expandable && isOpen(item)) setOpen(item, false)\n else if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n const rows = [...(treeElement.current?.querySelectorAll<HTMLElement>('[role=\"treeitem\"]') ?? [])]\n rows[rows.indexOf(event.currentTarget) + (event.key === 'ArrowDown' ? 1 : -1)]?.focus()\n } else return\n event.preventDefault()\n }\n\n // --- filters ---------------------------------------------------------------------------------------------------\n\n const kindsOf = (item: TreeItem) => filterKinds(item, { structure, activeId, kinds, t })\n const hiddenBelow = (item: TreeItem) => nodeFilters[item.key] ?? []\n const isFiltered = (item: TreeItem) => hiddenBelow(item).length > 0\n const showFilter = (item: TreeItem) =>\n kindsOf(item).length > 0 && (item.type === 'root' || item.type === 'folder' || !!item.children?.length || isFiltered(item))\n\n // Filters of items that no longer exist (deleted documents, Bausteine) are ignored, so „Filter zurücksetzen“ only shows\n // while something visible is filtered.\n const visibleKeys = collectKeys(unfilteredItems, new Set())\n const hasFilters = Object.entries(nodeFilters).some(([key, hidden]) => hidden.length > 0 && visibleKeys.has(key))\n\n // --- creating ----------------------------------------------------------------------------------------------------\n\n async function importIntoComposite(blockId: string | null) {\n const files = await pickFiles('*/*')\n if (!files.length || !editor || editor.isDestroyed) return\n await importFiles(editor, files, compositeInsertPos(editor, blockId))\n if (blockId) openOutline(blockId)\n }\n\n function createCompositeFolder(blockId: string | null) {\n if (!editor || editor.isDestroyed) return\n const pos = compositeInsertPos(editor, blockId)\n // The caret goes into the new composite's title: one level in, then the text inside it.\n editor.chain().insertContentAt(pos, compositeJSON('folder')).setTextSelection(pos + 2).focus().scrollIntoView().run()\n if (blockId) openOutline(blockId)\n }\n\n async function createDocumentIn(folder: string) {\n setSelectedFolder(folder)\n const doc = await store.create(undefined, folder).catch(() => undefined)\n if (!doc) return\n model.expand(ancestorKeys(folder))\n await store.open(doc.id).catch(() => undefined)\n }\n\n const folderError = creatingFolder?.draft.trim() ? store.validateName(creatingFolder.draft, folderNamesIn(tree, creatingFolder.parent)) : null\n\n function startCreateFolder(parent: string) {\n setSelectedFolder(parent)\n setCreatingFolder({ parent, draft: '' })\n }\n\n async function commitCreateFolder() {\n const current = creatingFolderRef.current\n if (!current) return\n if (!current.draft.trim()) {\n setCreatingFolder(null)\n return\n }\n // An invalid name keeps the field open with its inline error.\n if (folderError) return\n setCreatingFolder(null)\n const created = await store.createFolder(joinPath(current.parent, current.draft.trim())).then(() => true, () => false)\n if (created) model.expand(ancestorKeys(current.parent))\n }\n\n // --- rename (folders; a document is renamed in the editor's title field) ---------------------------------------------\n\n const renameErrorOf = (current: Renaming | null) =>\n !current || current.draft.trim() === current.item.label\n ? null\n : store.validateName(current.draft, folderNamesIn(tree, parentPath(current.item.path!), current.item.path))\n const renameError = renameErrorOf(renaming)\n\n async function commitRename() {\n const current = renamingRef.current\n if (!current) return\n // An invalid name keeps the field open with its inline error.\n if (renameErrorOf(current)) return\n const name = current.draft.trim()\n setRenaming(null)\n if (name === current.item.label) return\n const to = joinPath(parentPath(current.item.path!), name)\n const moved = await store.moveFolder(current.item.path!, to).then(() => true, () => false)\n if (moved) followFolderMove(current.item.path!, to)\n }\n\n function onRenameBlur() {\n const current = renamingRef.current\n if (!current) return\n const error = renameErrorOf(current)\n if (error) {\n notify({ kind: 'warning', title: t('tree.folder.renameFailed'), description: error })\n setRenaming(null)\n return\n }\n return commitRename()\n }\n\n // --- delete ----------------------------------------------------------------------------------------------------\n\n async function restore(removed: { restore: () => Promise<{ id: string }> }) {\n const restored = await removed.restore().catch(() => undefined)\n if (restored) await store.open(restored.id).catch(() => undefined)\n }\n\n async function removeWithUndo(id: string, folder: string) {\n // The store leaves an open document and waits for its page teardown (which destroys its collaboration provider)\n // before it deletes.\n const removed = await store.remove(id, folder).catch(() => undefined)\n if (!removed) return\n toast(t('tree.removed'), {\n description: removed.title,\n icon: <Icon name=\"i-lucide-trash-2\" />,\n action: { label: t('tree.undo'), onClick: () => { void restore(removed) } },\n })\n }\n\n async function removeItem(item: TreeItem) {\n setRemoving(item.key)\n try {\n if (item.type === 'document') await removeWithUndo(item.id!, item.folder ?? '')\n // The server refuses non-empty folders; the toast then reads „Ordner ist nicht leer“.\n else await store.removeFolder(item.path!).catch(() => undefined)\n } finally {\n setRemoving(null)\n }\n }\n\n // --- + menu ----------------------------------------------------------------------------------------------------\n\n /**\n * What can be created below `item`: folders of the store take documents and sub-folders, the open file (as the root or\n * as a document row) and its Ordner/Dokument composites take Ordner and imported Dokument Bausteine. Other rows take\n * nothing. Folders also list „Umbenennen“ here: renaming a document happens in the editor's title field.\n */\n function addMenu(item: TreeItem): { label: string, icon: string, onSelect: () => void }[][] {\n const groups: { label: string, icon: string, onSelect: () => void }[][] = []\n if (item.key !== VIRTUAL_ROOT && (item.type === 'root' || item.type === 'folder')) {\n const path = item.path!\n groups.push([\n { label: t('tree.menu.newDocument'), icon: 'i-lucide-file-plus', onSelect: () => void createDocumentIn(path) },\n { label: item.type === 'root' ? t('tree.menu.newFolder') : t('tree.menu.newSubfolder'), icon: 'i-lucide-folder-plus', onSelect: () => startCreateFolder(path) },\n ])\n } else if (holdsComposites(item, { structure, editor, activeId })) {\n const blockId = item.blockId ?? null\n groups.push([\n { label: t('tree.menu.newCompositeFolder'), icon: 'i-lucide-folder-plus', onSelect: () => { createCompositeFolder(blockId); if (!blockId) model.expand([item.key]) } },\n { label: t('tree.menu.importFile'), icon: 'i-lucide-file-up', onSelect: () => { void importIntoComposite(blockId); if (!blockId) model.expand([item.key]) } },\n ])\n }\n if (item.type === 'folder') groups.push([{ label: t('tree.menu.rename'), icon: 'i-lucide-pencil', onSelect: () => setRenaming({ item, draft: item.label }) }])\n return groups\n }\n\n // --- drag & drop -----------------------------------------------------------------------------------------------\n\n const isDraggable = (item: TreeItem) => renaming?.item.key !== item.key && isDraggableItem(item, virtual)\n\n function onDragStart(event: DragEvent<HTMLDivElement>, item: TreeItem) {\n if (!isDraggable(item)) return event.preventDefault()\n const payload = dragPayloadOf(item)\n dragging.current = payload\n event.dataTransfer.setData(DRAG_MIME, JSON.stringify(payload))\n event.dataTransfer.effectAllowed = 'move'\n }\n\n function onDragOver(event: DragEvent<HTMLDivElement>, item: TreeItem) {\n if (!canDrop(dragging.current, item, { virtual, editor })) return\n event.preventDefault()\n event.dataTransfer.dropEffect = 'move'\n setDropKey(item.key)\n }\n\n function onDragLeave(event: DragEvent<HTMLDivElement>, item: TreeItem) {\n if (dropKey === item.key && !event.currentTarget.contains(event.relatedTarget as Node | null)) setDropKey(null)\n }\n\n function endDrag() {\n dragging.current = null\n setDropKey(null)\n }\n\n async function onDrop(event: DragEvent<HTMLDivElement>, target: TreeItem) {\n const raw = event.dataTransfer.getData(DRAG_MIME)\n endDrag()\n if (!raw) return\n event.preventDefault()\n const source = JSON.parse(raw) as DragPayload\n if (!canDrop(source, target, { virtual, editor })) return\n if (source.type === 'composite') {\n const targetId = target.key === VIRTUAL_ROOT ? null : target.blockId!\n if (editor && moveBlockInto(editor, source.blockId, targetId) && targetId) openOutline(targetId)\n return\n }\n const targetPath = target.path!\n if (source.type === 'document') {\n const moved = await store.move(source.id!, targetPath).then(() => true, () => false)\n if (moved) model.expand(ancestorKeys(targetPath))\n return\n }\n const to = joinPath(targetPath, source.path.slice(source.path.lastIndexOf('/') + 1))\n const moved = await store.moveFolder(source.path, to).then(() => true, () => false)\n if (moved) followFolderMove(source.path, to)\n }\n\n // --- rendering -------------------------------------------------------------------------------------------------\n\n const filterMenu = (item: TreeItem) => {\n const hidden = hiddenBelow(item)\n return (\n <DropdownMenuContent align=\"start\" className=\"w-auto\">\n <DropdownMenuLabel>{t('tree.filter.show')}</DropdownMenuLabel>\n {kindsOf(item).map(kind => (\n <DropdownMenuCheckboxItem\n key={kind.id}\n checked={!hidden.includes(kind.id)}\n onCheckedChange={visible => model.setKindHidden(item.key, kind.id, !visible)}\n // Stay open: several kinds are usually toggled in one go.\n onSelect={event => event.preventDefault()}\n >\n <Icon name={kind.icon} />\n {kind.label}\n </DropdownMenuCheckboxItem>\n ))}\n {hidden.length > 0 && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onSelect={() => model.clearFilter(item.key)}>\n <Icon name=\"i-lucide-filter-x\" />\n {t('tree.filter.reset')}\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n )\n }\n\n const renderRow = (item: TreeItem, open: boolean, level: number) => {\n const chevron = item.type === 'root' || item.type === 'folder' || !!item.children?.length\n const selectedInVirtual = virtual && (item.key === VIRTUAL_ROOT ? virtualTarget === null : !!item.blockId && item.blockId === virtualTarget)\n const add = addMenu(item)\n const filtered = isFiltered(item)\n return (\n <div\n role=\"treeitem\"\n tabIndex={0}\n aria-level={level + 1}\n aria-expanded={chevron ? open : undefined}\n aria-selected={item.id !== undefined && item.id === activeId ? true : undefined}\n className={cn(\n 'group flex w-full cursor-pointer items-center gap-1.5 rounded-md px-2 py-1 text-sm select-none outline-none hover:bg-muted/60 focus-visible:ring-1 focus-visible:ring-ring',\n item.id !== undefined && item.id === activeId && 'bg-primary/10 font-medium text-primary',\n item.type !== 'document' && item.path === selectedFolder && 'bg-muted',\n selectedInVirtual && 'bg-muted',\n dropKey === item.key && 'ring-1 ring-primary',\n )}\n draggable={isDraggable(item)}\n onClick={() => activate(item)}\n onKeyDown={event => onRowKeyDown(event, item)}\n onDragStart={event => onDragStart(event, item)}\n onDragOver={event => onDragOver(event, item)}\n onDragLeave={event => onDragLeave(event, item)}\n onDrop={event => void onDrop(event, item)}\n onDragEnd={endDrag}\n >\n {chevron\n ? (\n <span className=\"flex shrink-0\" onClick={event => onChevronClick(event, item)}>\n <Icon name=\"i-lucide-chevron-right\" className={cn('size-3.5 text-muted-foreground transition-transform', open && 'rotate-90')} />\n </span>\n )\n : isOutlineItem(item) && <span className=\"size-3.5 shrink-0\" />}\n <Icon name={item.icon} />\n\n {renaming?.item.key === item.key\n ? (\n <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <Input\n value={renaming.draft}\n className=\"h-6 min-w-0 px-2 text-xs md:text-xs\"\n autoFocus\n aria-label={t('tree.folder.name')}\n aria-invalid={!!renameError}\n onChange={event => setRenaming({ item: renaming.item, draft: event.target.value })}\n onFocus={event => event.currentTarget.select()}\n onClick={event => event.stopPropagation()}\n onKeyDown={(event) => {\n event.stopPropagation()\n if (event.key === 'Enter') void commitRename()\n else if (event.key === 'Escape') setRenaming(null)\n }}\n onBlur={() => void onRenameBlur()}\n />\n {renameError && <p className=\"text-xs text-destructive\" role=\"alert\" onClick={event => event.stopPropagation()}>{renameError}</p>}\n </div>\n )\n : (\n <>\n <span className=\"min-w-0 flex-1 truncate\" title={item.label}>{item.label}</span>\n {item.hasSummary && <Icon name=\"i-lucide-text-quote\" className=\"size-3.5 text-muted-foreground\" />}\n <div className=\"flex shrink-0\" onClick={event => event.stopPropagation()}>\n {showFilter(item) && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n size=\"icon-xs\"\n variant={filtered ? 'secondary' : 'ghost'}\n className={cn(!filtered && REVEAL_ON_HOVER)}\n title={t('tree.filter.title')}\n aria-label={t('tree.filter.title')}\n >\n <Icon name=\"i-lucide-filter\" />\n </Button>\n </DropdownMenuTrigger>\n {filterMenu(item)}\n </DropdownMenu>\n )}\n <div className=\"flex opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 has-data-[state=open]:opacity-100\">\n {add.length > 0 && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button size=\"icon-xs\" variant=\"ghost\" title={t('tree.add')} aria-label={t('tree.add')}>\n <Icon name=\"i-lucide-plus\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-auto\" onCloseAutoFocus={event => event.preventDefault()}>\n {add.map((group, index) => (\n <div key={index}>\n {index > 0 && <DropdownMenuSeparator />}\n {group.map(entry => (\n <DropdownMenuItem key={entry.label} onSelect={entry.onSelect}>\n <Icon name={entry.icon} />\n {entry.label}\n </DropdownMenuItem>\n ))}\n </div>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n {item.type === 'document' && documentActions?.({ id: item.id!, title: item.label })}\n {(item.type === 'document' || item.type === 'folder') && (\n <Button\n size=\"icon-xs\"\n variant=\"ghost\"\n className=\"text-destructive hover:text-destructive\"\n aria-label={t('tree.delete')}\n disabled={removing === item.key}\n onClick={() => void removeItem(item)}\n >\n <Icon name={removing === item.key ? 'i-lucide-loader-circle' : 'i-lucide-trash-2'} className={cn(removing === item.key && 'animate-spin')} />\n </Button>\n )}\n </div>\n </div>\n </>\n )}\n </div>\n )\n }\n\n const renderItems = (list: readonly TreeItem[], level: number): ReactNode => (\n <ul role={level === 0 ? 'tree' : 'group'} aria-label={level === 0 ? t('tree.aria') : undefined} ref={level === 0 ? treeElement : undefined} className={cn('m-0 list-none p-0', level > 0 && 'ml-3 border-l border-border pl-1')}>\n {list.map((item) => {\n const open = isOpen(item)\n return (\n <li key={item.key} role=\"none\">\n <Collapsible open={open}>\n {renderRow(item, open, level)}\n {item.children?.length ? <CollapsibleContent>{renderItems(item.children, level + 1)}</CollapsibleContent> : null}\n </Collapsible>\n </li>\n )\n })}\n </ul>\n )\n\n return (\n <div className={cn('flex flex-col gap-2', className)}>\n {hasFilters && (\n <div className=\"flex items-center\">\n <Button size=\"xs\" variant=\"secondary\" onClick={() => model.clearAllFilters()}>\n <Icon name=\"i-lucide-filter-x\" />\n {t('tree.filter.reset')}\n </Button>\n </div>\n )}\n\n {creatingFolder && (\n <div className=\"flex flex-col gap-1\">\n <Input\n value={creatingFolder.draft}\n className=\"h-7 text-sm md:text-sm\"\n autoFocus\n placeholder={creatingFolder.parent ? t('tree.folder.placeholderIn', { parent: creatingFolder.parent }) : t('tree.folder.placeholder')}\n aria-label={t('tree.folder.name')}\n aria-invalid={!!folderError}\n onChange={event => setCreatingFolder({ parent: creatingFolder.parent, draft: event.target.value })}\n onKeyDown={(event) => {\n if (event.key === 'Enter') void commitCreateFolder()\n else if (event.key === 'Escape') setCreatingFolder(null)\n }}\n onBlur={() => setCreatingFolder(null)}\n />\n {folderError && <p className=\"px-0.5 text-xs text-destructive\" role=\"alert\">{folderError}</p>}\n </div>\n )}\n\n {!tree && !virtual\n ? <p className=\"px-2.5 py-1.5 text-sm text-muted-foreground\">{tree === null ? t('tree.loadFailed') : t('tree.loading')}</p>\n : renderItems(items, 0)}\n\n {isEmpty && <p className=\"px-2.5 py-1.5 text-sm text-muted-foreground\">{t('tree.empty')}</p>}\n </div>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DocumentTree.js","names":[],"sources":["../../../src/tree/react/DocumentTree.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { type DragEvent, type KeyboardEvent, type ReactNode, useRef } from 'react'\nimport { Icon } from '../../react/Icon'\nimport { useEditorI18n } from '../../react/use-editor-i18n'\nimport { Button } from '../../react/ui/button'\nimport { Collapsible, CollapsibleContent } from '../../react/ui/collapsible'\nimport { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '../../react/ui/dropdown-menu'\nimport { Input } from '../../react/ui/input'\nimport { cn } from '../../react/ui/utils'\nimport type { DocumentStore } from '../store'\nimport { DRAG_MIME, type TreeItem } from '../tree-items'\nimport { rowOf } from '../tree-model'\nimport { useTreeModel } from './use-tree-model'\n\n// File browser of a document store: a „Dokumente“ root with folders and documents. It only renders the `TreeModel` (see\n// `tree-model.ts`), which owns what is open, filtered, selected, dragged and renamed; the Vue twin renders the same model.\n\n/** Props of `DocumentTree`. */\nexport interface DocumentTreeProps {\n /** Where documents and folders live; the host implements it (see `DocumentStore`). */\n store: DocumentStore\n /** Editor of the open document (its outline is listed under the document); `undefined` while none is mounted. */\n editor: Editor | undefined\n /** The folder new documents, folders and host imports go to (controlled). Omit it to let the tree keep it. Reset to `''` when it is deleted or moved away. */\n selectedFolder?: string\n onSelectedFolderChange?: (folder: string) => void\n /** Host actions on a document row, rendered next to its delete button (e.g. a download link). */\n documentActions?: (document: { id: string, title: string }) => ReactNode\n className?: string\n}\n\nconst REVEAL_ON_HOVER = 'opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 data-[state=open]:opacity-100'\nconst isOutlineItem = (item: TreeItem) => item.type === 'composite' || item.type === 'heading'\n\n/** The label with the part the title filter matched highlighted. */\nfunction Label({ label, match }: { label: string, match: readonly [number, number] | null }) {\n if (!match) return <>{label}</>\n const [at, length] = match\n return (\n <>\n {label.slice(0, at)}\n <mark className=\"rounded-sm bg-primary/20 text-inherit\">{label.slice(at, at + length)}</mark>\n {label.slice(at + length)}\n </>\n )\n}\n\n/** The file browser of a document store: folders and documents with their drag and drop, the open document's outline below it. */\nexport function DocumentTree({ store, editor, selectedFolder, onSelectedFolderChange, documentActions, className }: DocumentTreeProps) {\n const { t } = useEditorI18n()\n const binding = useTreeModel({ store, editor, selectedFolder, onSelectedFolderChange })\n const treeElement = useRef<HTMLUListElement>(null)\n if (!binding) return null\n const { model, state } = binding\n const { view, creatingFolder } = state\n\n // Folders and the root toggle through the row click; documents and outline entries (whose row click opens or reveals)\n // toggle through their chevron.\n function onChevronClick(event: { stopPropagation: () => void }, item: TreeItem) {\n if (item.type === 'root' || item.type === 'folder') return\n event.stopPropagation()\n model.toggle(item.key)\n }\n\n function onRowKeyDown(event: KeyboardEvent<HTMLDivElement>, item: TreeItem) {\n if (event.target !== event.currentTarget) return\n const { expandable, open } = rowOf(state, item, t)\n if (event.key === 'Enter' || event.key === ' ') model.activate(item)\n else if (event.key === 'ArrowRight' && expandable && !open) model.setOpen(item.key, true)\n else if (event.key === 'ArrowLeft' && expandable && open) model.setOpen(item.key, false)\n else if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n const rows = [...(treeElement.current?.querySelectorAll<HTMLElement>('[role=\"treeitem\"]') ?? [])]\n rows[rows.indexOf(event.currentTarget) + (event.key === 'ArrowDown' ? 1 : -1)]?.focus()\n } else return\n event.preventDefault()\n }\n\n // --- drag & drop -----------------------------------------------------------------------------------------------\n\n function onDragStart(event: DragEvent<HTMLDivElement>, item: TreeItem) {\n const payload = model.dragStart(item)\n if (!payload) return event.preventDefault()\n event.dataTransfer.setData(DRAG_MIME, JSON.stringify(payload))\n event.dataTransfer.effectAllowed = 'move'\n }\n\n function onDragOver(event: DragEvent<HTMLDivElement>, item: TreeItem) {\n const verdict = model.dragOver(item)\n if (!verdict) return\n // Only an allowed target takes the drop; over a refused one the browser shows „not allowed“ and the row shows why.\n if (verdict.ok) event.preventDefault()\n event.dataTransfer.dropEffect = verdict.ok ? 'move' : 'none'\n }\n\n function onDragLeave(event: DragEvent<HTMLDivElement>, item: TreeItem) {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) model.dragLeave(item)\n }\n\n function onDrop(event: DragEvent<HTMLDivElement>, item: TreeItem) {\n if (!state.drag) return\n event.preventDefault()\n void model.drop(item)\n }\n\n // --- rendering -------------------------------------------------------------------------------------------------\n\n const filterMenu = (item: TreeItem) => {\n const { filterKinds, hiddenKinds } = rowOf(state, item, t)\n return (\n <DropdownMenuContent align=\"start\" className=\"w-auto\">\n <DropdownMenuLabel>{t('tree.filter.show')}</DropdownMenuLabel>\n {filterKinds.map(kind => (\n <DropdownMenuCheckboxItem\n key={kind.id}\n checked={!hiddenKinds.includes(kind.id)}\n onCheckedChange={visible => model.setKindHidden(item.key, kind.id, !visible)}\n // Stay open: several kinds are usually toggled in one go.\n onSelect={event => event.preventDefault()}\n >\n <Icon name={kind.icon} />\n {kind.label}\n </DropdownMenuCheckboxItem>\n ))}\n {hiddenKinds.length > 0 && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onSelect={() => model.clearFilter(item.key)}>\n <Icon name=\"i-lucide-filter-x\" />\n {t('tree.filter.reset')}\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n )\n }\n\n const renderRow = (item: TreeItem, level: number) => {\n const row = rowOf(state, item, t)\n const add = model.addMenu(item)\n const filtered = row.hiddenKinds.length > 0\n const refused = row.drop && !row.drop.ok ? row.drop.reason : null\n return (\n <>\n <div\n role=\"treeitem\"\n tabIndex={0}\n aria-level={level + 1}\n aria-expanded={row.expandable ? row.open : undefined}\n aria-selected={row.active ? true : undefined}\n className={cn(\n 'group flex w-full cursor-pointer items-center gap-1.5 rounded-md px-2 py-1 text-sm select-none outline-none hover:bg-muted/60 focus-visible:ring-1 focus-visible:ring-ring',\n row.active && 'bg-primary/10 font-medium text-primary',\n row.selected && 'bg-muted',\n row.drop?.ok && 'ring-1 ring-primary',\n refused && 'bg-destructive/10 ring-1 ring-destructive',\n )}\n draggable={model.isDraggable(item)}\n onClick={() => model.activate(item)}\n onKeyDown={event => onRowKeyDown(event, item)}\n onDragStart={event => onDragStart(event, item)}\n onDragOver={event => onDragOver(event, item)}\n onDragLeave={event => onDragLeave(event, item)}\n onDrop={event => onDrop(event, item)}\n onDragEnd={() => model.dragEnd()}\n >\n {row.expandable\n ? (\n <span className=\"flex shrink-0\" onClick={event => onChevronClick(event, item)}>\n <Icon name=\"i-lucide-chevron-right\" className={cn('size-3.5 text-muted-foreground transition-transform', row.open && 'rotate-90')} />\n </span>\n )\n : isOutlineItem(item) && <span className=\"size-3.5 shrink-0\" />}\n <Icon name={item.icon} />\n\n {row.renaming\n ? (\n <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <Input\n value={row.renaming.draft}\n className=\"h-6 min-w-0 px-2 text-xs md:text-xs\"\n autoFocus\n aria-label={t('tree.folder.name')}\n aria-invalid={!!row.renaming.error}\n onChange={event => model.setRenameDraft(event.target.value)}\n onFocus={event => event.currentTarget.select()}\n onClick={event => event.stopPropagation()}\n onKeyDown={(event) => {\n event.stopPropagation()\n if (event.key === 'Enter') void model.commitRename()\n else if (event.key === 'Escape') model.cancelRename()\n }}\n onBlur={() => void model.blurRename()}\n />\n {row.renaming.error && <p className=\"text-xs text-destructive\" role=\"alert\" onClick={event => event.stopPropagation()}>{row.renaming.error}</p>}\n </div>\n )\n : (\n <>\n <span className=\"min-w-0 flex-1 truncate\" title={item.label}><Label label={item.label} match={row.match} /></span>\n {item.hasSummary && <Icon name=\"i-lucide-text-quote\" className=\"size-3.5 text-muted-foreground\" />}\n <div className=\"flex shrink-0\" onClick={event => event.stopPropagation()}>\n {row.showFilter && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n size=\"icon-xs\"\n variant={filtered ? 'secondary' : 'ghost'}\n className={cn(!filtered && REVEAL_ON_HOVER)}\n title={t('tree.filter.title')}\n aria-label={t('tree.filter.title')}\n >\n <Icon name=\"i-lucide-filter\" />\n </Button>\n </DropdownMenuTrigger>\n {filterMenu(item)}\n </DropdownMenu>\n )}\n <div className=\"flex opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 has-data-[state=open]:opacity-100\">\n {add.length > 0 && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button size=\"icon-xs\" variant=\"ghost\" title={t('tree.add')} aria-label={t('tree.add')}>\n <Icon name=\"i-lucide-plus\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-auto\" onCloseAutoFocus={event => event.preventDefault()}>\n {add.map((group, index) => (\n <div key={index}>\n {index > 0 && <DropdownMenuSeparator />}\n {group.map(entry => (\n <DropdownMenuItem key={entry.label} onSelect={entry.run}>\n <Icon name={entry.icon} />\n {entry.label}\n </DropdownMenuItem>\n ))}\n </div>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n {item.type === 'document' && documentActions?.({ id: item.id!, title: item.label })}\n {(item.type === 'document' || item.type === 'folder') && (\n <Button\n size=\"icon-xs\"\n variant=\"ghost\"\n className=\"text-destructive hover:text-destructive\"\n aria-label={t('tree.delete')}\n disabled={row.removing}\n onClick={() => void model.remove(item)}\n >\n <Icon name={row.removing ? 'i-lucide-loader-circle' : 'i-lucide-trash-2'} className={cn(row.removing && 'animate-spin')} />\n </Button>\n )}\n </div>\n </div>\n </>\n )}\n </div>\n {refused && <p className=\"px-2 pt-0.5 text-xs text-destructive\" role=\"status\">{refused}</p>}\n </>\n )\n }\n\n const renderItems = (list: readonly TreeItem[], level: number): ReactNode => (\n <ul role={level === 0 ? 'tree' : 'group'} aria-label={level === 0 ? t('tree.aria') : undefined} ref={level === 0 ? treeElement : undefined} className={cn('m-0 list-none p-0', level > 0 && 'ml-3 border-l border-border pl-1')}>\n {list.map(item => (\n <li key={item.key} role=\"none\">\n <Collapsible open={rowOf(state, item, t).open}>\n {renderRow(item, level)}\n {item.children?.length ? <CollapsibleContent>{renderItems(item.children, level + 1)}</CollapsibleContent> : null}\n </Collapsible>\n </li>\n ))}\n </ul>\n )\n\n return (\n <div className={cn('flex flex-col gap-2', className)}>\n <div className=\"relative\">\n <Icon name=\"i-lucide-search\" className=\"pointer-events-none absolute top-1/2 left-2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={state.filterText}\n className=\"h-7 pr-7 pl-7 text-sm md:text-sm\"\n placeholder={t('tree.filterText.placeholder')}\n aria-label={t('tree.filterText.placeholder')}\n onChange={event => model.setFilterText(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Escape') model.setFilterText('')\n }}\n />\n {state.filterText && (\n <Button\n size=\"icon-xs\"\n variant=\"ghost\"\n className=\"absolute top-1/2 right-1 -translate-y-1/2\"\n aria-label={t('tree.filterText.clear')}\n onClick={() => model.setFilterText('')}\n >\n <Icon name=\"i-lucide-x\" />\n </Button>\n )}\n </div>\n\n {view.hasKindFilters && (\n <div className=\"flex items-center\">\n <Button size=\"xs\" variant=\"secondary\" onClick={() => model.clearAllFilters()}>\n <Icon name=\"i-lucide-filter-x\" />\n {t('tree.filter.reset')}\n </Button>\n </div>\n )}\n\n {creatingFolder && (\n <div className=\"flex flex-col gap-1\">\n <Input\n value={creatingFolder.draft}\n className=\"h-7 text-sm md:text-sm\"\n autoFocus\n placeholder={creatingFolder.parent ? t('tree.folder.placeholderIn', { parent: creatingFolder.parent }) : t('tree.folder.placeholder')}\n aria-label={t('tree.folder.name')}\n aria-invalid={!!creatingFolder.error}\n onChange={event => model.setFolderDraft(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') void model.commitCreateFolder()\n else if (event.key === 'Escape') model.cancelCreateFolder()\n }}\n onBlur={() => model.cancelCreateFolder()}\n />\n {creatingFolder.error && <p className=\"px-0.5 text-xs text-destructive\" role=\"alert\">{creatingFolder.error}</p>}\n </div>\n )}\n\n {!state.tree && !state.virtual\n ? <p className=\"px-2.5 py-1.5 text-sm text-muted-foreground\">{state.tree === null ? t('tree.loadFailed') : t('tree.loading')}</p>\n : renderItems(view.items, 0)}\n\n {view.noMatches && <p className=\"px-2.5 py-1.5 text-sm text-muted-foreground\">{t('tree.noMatches')}</p>}\n {view.isEmpty && <p className=\"px-2.5 py-1.5 text-sm text-muted-foreground\">{t('tree.empty')}</p>}\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useEditorContext } from "../../react/context.js";
|
|
2
|
+
import { useEditorI18n } from "../../react/use-editor-i18n.js";
|
|
3
|
+
import { Icon } from "../../react/Icon.js";
|
|
4
|
+
import { useEditorSettings } from "../../react/settings.js";
|
|
5
|
+
import { useNotifier } from "../../react/use-notifier.js";
|
|
6
|
+
import { useFileImport } from "../../features/media/react.js";
|
|
7
|
+
import { treeView } from "../tree-view-model.js";
|
|
8
|
+
import { createTreeModel } from "../tree-model.js";
|
|
9
|
+
import { createTreeExecutor, treeEditorOf } from "../tree-host.js";
|
|
10
|
+
import { createElement, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
11
|
+
import { toast } from "sonner";
|
|
12
|
+
//#region src/tree/react/use-tree-model.ts
|
|
13
|
+
const noSubscribe = () => () => {};
|
|
14
|
+
const noState = () => void 0;
|
|
15
|
+
/**
|
|
16
|
+
* The tree model (`createTreeModel`) of one React `DocumentTree`: over the shared view state of the editor context, with
|
|
17
|
+
* sonner toasts and the React file import, following the open editor and the `blocks` settings; re-renders when its
|
|
18
|
+
* state changes. `selectedFolder` is controlled when given (omit it to let the model keep the selection); the model's
|
|
19
|
+
* selection, including its fall back to the root when the folder is gone, is reported to `onSelectedFolderChange`.
|
|
20
|
+
* `undefined` for the first render: the model is created in an effect, paired with its `dispose`, so StrictMode's
|
|
21
|
+
* mount, cleanup, mount leaves exactly one live model.
|
|
22
|
+
*/
|
|
23
|
+
function useTreeModel(options) {
|
|
24
|
+
const { store, editor, selectedFolder, onSelectedFolderChange } = options;
|
|
25
|
+
const view = treeView(useEditorContext());
|
|
26
|
+
const { t } = useEditorI18n();
|
|
27
|
+
const { settings } = useEditorSettings();
|
|
28
|
+
const { structure, treeRoot } = settings.blocks;
|
|
29
|
+
const { importFiles } = useFileImport(editor);
|
|
30
|
+
const notify = useNotifier(editor);
|
|
31
|
+
const treeEditor = useMemo(() => editor ? treeEditorOf(editor, t) : void 0, [editor, t]);
|
|
32
|
+
const latest = useRef({
|
|
33
|
+
store,
|
|
34
|
+
editor,
|
|
35
|
+
importFiles,
|
|
36
|
+
notify,
|
|
37
|
+
selectedFolder,
|
|
38
|
+
onSelectedFolderChange,
|
|
39
|
+
context: {
|
|
40
|
+
editor: treeEditor,
|
|
41
|
+
structure,
|
|
42
|
+
treeRoot
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
latest.current = {
|
|
46
|
+
store,
|
|
47
|
+
editor,
|
|
48
|
+
importFiles,
|
|
49
|
+
notify,
|
|
50
|
+
selectedFolder,
|
|
51
|
+
onSelectedFolderChange,
|
|
52
|
+
context: {
|
|
53
|
+
editor: treeEditor,
|
|
54
|
+
structure,
|
|
55
|
+
treeRoot
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const [execute] = useState(() => createTreeExecutor({
|
|
59
|
+
store: () => latest.current.store,
|
|
60
|
+
editor: () => latest.current.editor,
|
|
61
|
+
importFiles: (instance, files, pos) => latest.current.importFiles(instance, files, pos),
|
|
62
|
+
warn: (title, description) => latest.current.notify({
|
|
63
|
+
kind: "warning",
|
|
64
|
+
title,
|
|
65
|
+
description
|
|
66
|
+
}),
|
|
67
|
+
deleted: (title, undo) => {
|
|
68
|
+
toast(t("tree.removed"), {
|
|
69
|
+
description: title,
|
|
70
|
+
icon: createElement(Icon, { name: "i-lucide-trash-2" }),
|
|
71
|
+
action: {
|
|
72
|
+
label: t("tree.undo"),
|
|
73
|
+
onClick: undo
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}));
|
|
78
|
+
const [model, setModel] = useState();
|
|
79
|
+
const reported = useRef(selectedFolder ?? "");
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const next = createTreeModel({
|
|
82
|
+
t,
|
|
83
|
+
store,
|
|
84
|
+
view,
|
|
85
|
+
execute,
|
|
86
|
+
context: latest.current.context,
|
|
87
|
+
selectedFolder: latest.current.selectedFolder
|
|
88
|
+
});
|
|
89
|
+
setModel(next);
|
|
90
|
+
return () => next.dispose();
|
|
91
|
+
}, [
|
|
92
|
+
t,
|
|
93
|
+
store,
|
|
94
|
+
view,
|
|
95
|
+
execute
|
|
96
|
+
]);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
model?.setContext({
|
|
99
|
+
editor: treeEditor,
|
|
100
|
+
structure,
|
|
101
|
+
treeRoot
|
|
102
|
+
});
|
|
103
|
+
}, [
|
|
104
|
+
model,
|
|
105
|
+
treeEditor,
|
|
106
|
+
structure,
|
|
107
|
+
treeRoot
|
|
108
|
+
]);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (selectedFolder === void 0 || selectedFolder === reported.current) return;
|
|
111
|
+
reported.current = selectedFolder;
|
|
112
|
+
model?.setSelectedFolder(selectedFolder);
|
|
113
|
+
}, [model, selectedFolder]);
|
|
114
|
+
const state = useSyncExternalStore(model ? model.state.subscribe : noSubscribe, model ? model.state.get : noState, model ? model.state.get : noState);
|
|
115
|
+
const folder = state?.selectedFolder;
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (folder === void 0 || folder === reported.current) return;
|
|
118
|
+
reported.current = folder;
|
|
119
|
+
latest.current.onSelectedFolderChange?.(folder);
|
|
120
|
+
}, [folder]);
|
|
121
|
+
return model && state ? {
|
|
122
|
+
model,
|
|
123
|
+
state
|
|
124
|
+
} : void 0;
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
export { useTreeModel };
|
|
128
|
+
|
|
129
|
+
//# sourceMappingURL=use-tree-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-tree-model.js","names":[],"sources":["../../../src/tree/react/use-tree-model.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { createElement, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'\nimport { toast } from 'sonner'\nimport { useFileImport } from '../../features/media/react'\nimport { useEditorContext } from '../../react/context'\nimport { Icon } from '../../react/Icon'\nimport { useEditorSettings } from '../../react/settings'\nimport { useEditorI18n } from '../../react/use-editor-i18n'\nimport { useNotifier } from '../../react/use-notifier'\nimport type { DocumentStore } from '../store'\nimport { createTreeExecutor, treeEditorOf } from '../tree-host'\nimport { createTreeModel, type TreeModel, type TreeState } from '../tree-model'\nimport { treeView } from '../tree-view-model'\n\n/** The model of a `DocumentTree` and its state. */\nexport interface TreeModelBinding {\n /** The model of the current store; replaced when the store changes. */\n model: TreeModel\n state: TreeState\n}\n\nconst noSubscribe = () => () => {}\nconst noState = () => undefined\n\n/**\n * The tree model (`createTreeModel`) of one React `DocumentTree`: over the shared view state of the editor context, with\n * sonner toasts and the React file import, following the open editor and the `blocks` settings; re-renders when its\n * state changes. `selectedFolder` is controlled when given (omit it to let the model keep the selection); the model's\n * selection, including its fall back to the root when the folder is gone, is reported to `onSelectedFolderChange`.\n * `undefined` for the first render: the model is created in an effect, paired with its `dispose`, so StrictMode's\n * mount, cleanup, mount leaves exactly one live model.\n */\nexport function useTreeModel(options: {\n store: DocumentStore\n editor: Editor | undefined\n selectedFolder?: string\n onSelectedFolderChange?: (folder: string) => void\n}): TreeModelBinding | undefined {\n const { store, editor, selectedFolder, onSelectedFolderChange } = options\n const view = treeView(useEditorContext())\n const { t } = useEditorI18n()\n const { settings } = useEditorSettings()\n const { structure, treeRoot } = settings.blocks\n const { importFiles } = useFileImport(editor)\n const notify = useNotifier(editor)\n const treeEditor = useMemo(() => (editor ? treeEditorOf(editor, t) : undefined), [editor, t])\n\n // What the executor and the model read when they run, not when the render that created them ran.\n const latest = useRef({ store, editor, importFiles, notify, selectedFolder, onSelectedFolderChange, context: { editor: treeEditor, structure, treeRoot } })\n latest.current = { store, editor, importFiles, notify, selectedFolder, onSelectedFolderChange, context: { editor: treeEditor, structure, treeRoot } }\n\n const [execute] = useState(() => createTreeExecutor({\n store: () => latest.current.store,\n editor: () => latest.current.editor,\n importFiles: (instance, files, pos) => latest.current.importFiles(instance, files, pos),\n warn: (title, description) => latest.current.notify({ kind: 'warning', title, description }),\n deleted: (title, undo) => {\n toast(t('tree.removed'), {\n description: title,\n icon: createElement(Icon, { name: 'i-lucide-trash-2' }),\n action: { label: t('tree.undo'), onClick: undo },\n })\n },\n }))\n\n const [model, setModel] = useState<TreeModel>()\n const reported = useRef(selectedFolder ?? '')\n\n useEffect(() => {\n const next = createTreeModel({ t, store, view, execute, context: latest.current.context, selectedFolder: latest.current.selectedFolder })\n setModel(next)\n return () => next.dispose()\n }, [t, store, view, execute])\n\n useEffect(() => {\n model?.setContext({ editor: treeEditor, structure, treeRoot })\n }, [model, treeEditor, structure, treeRoot])\n\n useEffect(() => {\n if (selectedFolder === undefined || selectedFolder === reported.current) return\n reported.current = selectedFolder\n model?.setSelectedFolder(selectedFolder)\n }, [model, selectedFolder])\n\n const state = useSyncExternalStore<TreeState | undefined>(model ? model.state.subscribe : noSubscribe, model ? model.state.get : noState, model ? model.state.get : noState)\n const folder = state?.selectedFolder\n useEffect(() => {\n if (folder === undefined || folder === reported.current) return\n reported.current = folder\n latest.current.onSelectedFolderChange?.(folder)\n }, [folder])\n\n return model && state ? { model, state } : undefined\n}\n"],"mappings":";;;;;;;;;;;;AAqBA,MAAM,0BAA0B,CAAC;AACjC,MAAM,gBAAgB,KAAA;;;;;;;;;AAUtB,SAAgB,aAAa,SAKI;CAC/B,MAAM,EAAE,OAAO,QAAQ,gBAAgB,2BAA2B;CAClE,MAAM,OAAO,SAAS,iBAAiB,CAAC;CACxC,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,EAAE,aAAa,kBAAkB;CACvC,MAAM,EAAE,WAAW,aAAa,SAAS;CACzC,MAAM,EAAE,gBAAgB,cAAc,MAAM;CAC5C,MAAM,SAAS,YAAY,MAAM;CACjC,MAAM,aAAa,cAAe,SAAS,aAAa,QAAQ,CAAC,IAAI,KAAA,GAAY,CAAC,QAAQ,CAAC,CAAC;CAG5F,MAAM,SAAS,OAAO;EAAE;EAAO;EAAQ;EAAa;EAAQ;EAAgB;EAAwB,SAAS;GAAE,QAAQ;GAAY;GAAW;EAAS;CAAE,CAAC;CAC1J,OAAO,UAAU;EAAE;EAAO;EAAQ;EAAa;EAAQ;EAAgB;EAAwB,SAAS;GAAE,QAAQ;GAAY;GAAW;EAAS;CAAE;CAEpJ,MAAM,CAAC,WAAW,eAAe,mBAAmB;EAClD,aAAa,OAAO,QAAQ;EAC5B,cAAc,OAAO,QAAQ;EAC7B,cAAc,UAAU,OAAO,QAAQ,OAAO,QAAQ,YAAY,UAAU,OAAO,GAAG;EACtF,OAAO,OAAO,gBAAgB,OAAO,QAAQ,OAAO;GAAE,MAAM;GAAW;GAAO;EAAY,CAAC;EAC3F,UAAU,OAAO,SAAS;GACxB,MAAM,EAAE,cAAc,GAAG;IACvB,aAAa;IACb,MAAM,cAAc,MAAM,EAAE,MAAM,mBAAmB,CAAC;IACtD,QAAQ;KAAE,OAAO,EAAE,WAAW;KAAG,SAAS;IAAK;GACjD,CAAC;EACH;CACF,CAAC,CAAC;CAEF,MAAM,CAAC,OAAO,YAAY,SAAoB;CAC9C,MAAM,WAAW,OAAO,kBAAkB,EAAE;CAE5C,gBAAgB;EACd,MAAM,OAAO,gBAAgB;GAAE;GAAG;GAAO;GAAM;GAAS,SAAS,OAAO,QAAQ;GAAS,gBAAgB,OAAO,QAAQ;EAAe,CAAC;EACxI,SAAS,IAAI;EACb,aAAa,KAAK,QAAQ;CAC5B,GAAG;EAAC;EAAG;EAAO;EAAM;CAAO,CAAC;CAE5B,gBAAgB;EACd,OAAO,WAAW;GAAE,QAAQ;GAAY;GAAW;EAAS,CAAC;CAC/D,GAAG;EAAC;EAAO;EAAY;EAAW;CAAQ,CAAC;CAE3C,gBAAgB;EACd,IAAI,mBAAmB,KAAA,KAAa,mBAAmB,SAAS,SAAS;EACzE,SAAS,UAAU;EACnB,OAAO,kBAAkB,cAAc;CACzC,GAAG,CAAC,OAAO,cAAc,CAAC;CAE1B,MAAM,QAAQ,qBAA4C,QAAQ,MAAM,MAAM,YAAY,aAAa,QAAQ,MAAM,MAAM,MAAM,SAAS,QAAQ,MAAM,MAAM,MAAM,OAAO;CAC3K,MAAM,SAAS,OAAO;CACtB,gBAAgB;EACd,IAAI,WAAW,KAAA,KAAa,WAAW,SAAS,SAAS;EACzD,SAAS,UAAU;EACnB,OAAO,QAAQ,yBAAyB,MAAM;CAChD,GAAG,CAAC,MAAM,CAAC;CAEX,OAAO,SAAS,QAAQ;EAAE;EAAO;CAAM,IAAI,KAAA;AAC7C"}
|
package/dist/tree/react.js
CHANGED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { findBlockById } from "../core/schema/block-ids.js";
|
|
2
|
+
import { compositeJSON, compositeKindsOf } from "../features/composites/nodes.js";
|
|
3
|
+
import { revealBlockInEditor } from "../outline/heading-outline.js";
|
|
4
|
+
import { moveBlockInto, moveIntoVerdict } from "../features/composites/outline.js";
|
|
5
|
+
import { pickFiles } from "../features/media/upload.js";
|
|
6
|
+
import { readOutline } from "./tree-items.js";
|
|
7
|
+
//#region src/tree/tree-host.ts
|
|
8
|
+
/** The model's view of `editor`: its Baustein kinds, outline and move verdicts (worded in the language of `t`), read live. Create it once per editor. */
|
|
9
|
+
function treeEditorOf(editor, t) {
|
|
10
|
+
return {
|
|
11
|
+
kinds: compositeKindsOf(editor),
|
|
12
|
+
outline: (structure) => readOutline(editor, structure, t),
|
|
13
|
+
moveVerdict: (id, targetId) => editor.isDestroyed ? {
|
|
14
|
+
ok: false,
|
|
15
|
+
reason: t("tree.drop.editorClosed")
|
|
16
|
+
} : moveIntoVerdict(editor, id, targetId, t),
|
|
17
|
+
onChange(listener) {
|
|
18
|
+
editor.on("update", listener);
|
|
19
|
+
return () => {
|
|
20
|
+
editor.off("update", listener);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** The open composite file: end of composite `blockId`'s body, else (`null`) the end of the document. */
|
|
26
|
+
function compositeInsertPos(editor, blockId) {
|
|
27
|
+
const { doc } = editor.state;
|
|
28
|
+
const target = blockId ? findBlockById(doc, blockId) : null;
|
|
29
|
+
return target?.node.type.name === "composite" ? target.pos + target.node.nodeSize - 1 : doc.content.size;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Runs the model's commands against the host's store and the editor. Every `store` method reports its own failure
|
|
33
|
+
* before it rejects, so a rejection only ends the command (`false`); the executor itself never rejects.
|
|
34
|
+
*/
|
|
35
|
+
function createTreeExecutor(host) {
|
|
36
|
+
const succeeded = () => true;
|
|
37
|
+
const failed = () => false;
|
|
38
|
+
const liveEditor = () => {
|
|
39
|
+
const editor = host.editor();
|
|
40
|
+
return editor && !editor.isDestroyed ? editor : void 0;
|
|
41
|
+
};
|
|
42
|
+
async function removeDocument(id, folder) {
|
|
43
|
+
const store = host.store();
|
|
44
|
+
const removed = await store.remove(id, folder).catch(() => void 0);
|
|
45
|
+
if (!removed) return false;
|
|
46
|
+
host.deleted(removed.title, () => {
|
|
47
|
+
removed.restore().then((restored) => store.open(restored.id)).catch(() => void 0);
|
|
48
|
+
});
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
async function importIntoComposite(targetId) {
|
|
52
|
+
const files = await pickFiles("*/*");
|
|
53
|
+
const editor = liveEditor();
|
|
54
|
+
if (!files.length || !editor) return false;
|
|
55
|
+
await host.importFiles(editor, files, compositeInsertPos(editor, targetId));
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return (command) => {
|
|
59
|
+
const store = host.store();
|
|
60
|
+
switch (command.type) {
|
|
61
|
+
case "openDocument": return store.open(command.id).then(succeeded, failed);
|
|
62
|
+
case "createDocument": return store.create(void 0, command.folder).then(async (doc) => {
|
|
63
|
+
await store.open(doc.id).catch(() => void 0);
|
|
64
|
+
return true;
|
|
65
|
+
}, failed);
|
|
66
|
+
case "moveDocument": return store.move(command.id, command.folder).then(succeeded, failed);
|
|
67
|
+
case "removeDocument": return removeDocument(command.id, command.folder);
|
|
68
|
+
case "createFolder": return store.createFolder(command.path).then(succeeded, failed);
|
|
69
|
+
case "moveFolder": return store.moveFolder(command.from, command.to).then(succeeded, failed);
|
|
70
|
+
case "removeFolder": return store.removeFolder(command.path).then(succeeded, failed);
|
|
71
|
+
case "moveBlock": {
|
|
72
|
+
const editor = liveEditor();
|
|
73
|
+
return !!editor && moveBlockInto(editor, command.id, command.targetId);
|
|
74
|
+
}
|
|
75
|
+
case "revealBlock": {
|
|
76
|
+
const editor = liveEditor();
|
|
77
|
+
if (editor) revealBlockInEditor(editor, {
|
|
78
|
+
id: command.id,
|
|
79
|
+
pos: command.pos
|
|
80
|
+
});
|
|
81
|
+
return !!editor;
|
|
82
|
+
}
|
|
83
|
+
case "createBausteinFolder": {
|
|
84
|
+
const editor = liveEditor();
|
|
85
|
+
if (!editor) return false;
|
|
86
|
+
const pos = compositeInsertPos(editor, command.targetId);
|
|
87
|
+
editor.chain().insertContentAt(pos, compositeJSON("folder")).setTextSelection(pos + 2).focus().scrollIntoView().run();
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
case "importFiles": return importIntoComposite(command.targetId);
|
|
91
|
+
case "warn":
|
|
92
|
+
host.warn(command.title, command.description);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
export { createTreeExecutor, treeEditorOf };
|
|
99
|
+
|
|
100
|
+
//# sourceMappingURL=tree-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-host.js","names":[],"sources":["../../src/tree/tree-host.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { findBlockById } from '../core/schema'\nimport { compositeJSON, compositeKindsOf, moveBlockInto, moveIntoVerdict } from '../features/composites'\nimport type { Translate } from '../i18n'\nimport { pickFiles } from '../features/media'\nimport { revealBlockInEditor } from '../outline'\nimport type { DocumentStore } from './store'\nimport type { TreeCommand, TreeEditor, TreeExecutor } from './tree-model'\nimport { readOutline } from './tree-items'\n\n// What connects a `TreeModel` to the world, shared by the Vue and the React `DocumentTree`: the open editor as the model\n// sees it, and the executor of the commands the model emits.\n\n/** The model's view of `editor`: its Baustein kinds, outline and move verdicts (worded in the language of `t`), read live. Create it once per editor. */\nexport function treeEditorOf(editor: Editor, t: Translate): TreeEditor {\n return {\n kinds: compositeKindsOf(editor),\n outline: structure => readOutline(editor, structure, t),\n moveVerdict: (id, targetId) => (editor.isDestroyed ? { ok: false, reason: t('tree.drop.editorClosed') } : moveIntoVerdict(editor, id, targetId, t)),\n onChange(listener) {\n editor.on('update', listener)\n return () => { editor.off('update', listener) }\n },\n }\n}\n\n/** What the executor needs from its framework binding. Every function reads the current value (props change). */\nexport interface TreeHost {\n store: () => DocumentStore\n editor: () => Editor | undefined\n /** The framework's file import (`useFileImport`). */\n importFiles: (editor: Editor, files: File[], pos: number) => Promise<unknown>\n /** Shows a warning (a toast). */\n warn: (title: string, description: string) => void\n /** Shows that a document was deleted, with an undo. */\n deleted: (title: string, undo: () => void) => void\n}\n\n/** The open composite file: end of composite `blockId`'s body, else (`null`) the end of the document. */\nfunction compositeInsertPos(editor: Editor, blockId: string | null): number {\n const { doc } = editor.state\n const target = blockId ? findBlockById(doc, blockId) : null\n return target?.node.type.name === 'composite' ? target.pos + target.node.nodeSize - 1 : doc.content.size\n}\n\n/**\n * Runs the model's commands against the host's store and the editor. Every `store` method reports its own failure\n * before it rejects, so a rejection only ends the command (`false`); the executor itself never rejects.\n */\nexport function createTreeExecutor(host: TreeHost): TreeExecutor {\n const succeeded = () => true\n const failed = () => false\n const liveEditor = () => {\n const editor = host.editor()\n return editor && !editor.isDestroyed ? editor : undefined\n }\n\n async function removeDocument(id: string, folder: string) {\n // The store leaves an open document and waits for its page teardown (which destroys its collaboration provider)\n // before it deletes.\n const store = host.store()\n const removed = await store.remove(id, folder).catch(() => undefined)\n if (!removed) return false\n host.deleted(removed.title, () => {\n void removed.restore().then(restored => store.open(restored.id)).catch(() => undefined)\n })\n return true\n }\n\n async function importIntoComposite(targetId: string | null) {\n const files = await pickFiles('*/*')\n const editor = liveEditor()\n if (!files.length || !editor) return false\n await host.importFiles(editor, files, compositeInsertPos(editor, targetId))\n return true\n }\n\n return (command: TreeCommand) => {\n const store = host.store()\n switch (command.type) {\n case 'openDocument':\n return store.open(command.id).then(succeeded, failed)\n case 'createDocument':\n return store.create(undefined, command.folder).then(async (doc) => {\n await store.open(doc.id).catch(() => undefined)\n return true\n }, failed)\n case 'moveDocument':\n return store.move(command.id, command.folder).then(succeeded, failed)\n case 'removeDocument':\n return removeDocument(command.id, command.folder)\n case 'createFolder':\n return store.createFolder(command.path).then(succeeded, failed)\n case 'moveFolder':\n return store.moveFolder(command.from, command.to).then(succeeded, failed)\n case 'removeFolder':\n // The server refuses non-empty folders; the toast then reads „Ordner ist nicht leer“.\n return store.removeFolder(command.path).then(succeeded, failed)\n case 'moveBlock': {\n const editor = liveEditor()\n return !!editor && moveBlockInto(editor, command.id, command.targetId)\n }\n case 'revealBlock': {\n const editor = liveEditor()\n if (editor) revealBlockInEditor(editor, { id: command.id, pos: command.pos })\n return !!editor\n }\n case 'createBausteinFolder': {\n const editor = liveEditor()\n if (!editor) return false\n const pos = compositeInsertPos(editor, command.targetId)\n // The caret goes into the new composite's title: one level in, then the text inside it.\n editor.chain().insertContentAt(pos, compositeJSON('folder')).setTextSelection(pos + 2).focus().scrollIntoView().run()\n return true\n }\n case 'importFiles':\n return importIntoComposite(command.targetId)\n case 'warn':\n host.warn(command.title, command.description)\n return true\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/tree/tree-items.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { compositeKind, defaultCompositeKinds, kindAccepts } from "../features/composites/kinds.js";
|
|
2
2
|
import { headingOutline } from "../outline/heading-outline.js";
|
|
3
|
-
import {
|
|
4
|
-
import { canMoveBlockInto, compositeOutline } from "../features/composites/outline.js";
|
|
3
|
+
import { compositeOutline } from "../features/composites/outline.js";
|
|
5
4
|
import { folderKey } from "./folder-paths.js";
|
|
6
5
|
//#region src/tree/tree-items.ts
|
|
7
6
|
/** Key of the virtual-mode root item (the open document). */
|
|
@@ -109,14 +108,6 @@ function virtualRootItem(label, outline, select) {
|
|
|
109
108
|
};
|
|
110
109
|
return item;
|
|
111
110
|
}
|
|
112
|
-
function collectParentKeys(list, into) {
|
|
113
|
-
for (const item of list) {
|
|
114
|
-
if (!item.children?.length) continue;
|
|
115
|
-
into.push(item.key);
|
|
116
|
-
collectParentKeys(item.children, into);
|
|
117
|
-
}
|
|
118
|
-
return into;
|
|
119
|
-
}
|
|
120
111
|
/** Adds the keys of `list` and all items below it to `into`; returns `into`. */
|
|
121
112
|
function collectKeys(list, into) {
|
|
122
113
|
for (const item of list) {
|
|
@@ -164,18 +155,25 @@ function applyNodeFilters(list, filters, inherited = /* @__PURE__ */ new Set())
|
|
|
164
155
|
}];
|
|
165
156
|
});
|
|
166
157
|
}
|
|
167
|
-
/**
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (
|
|
177
|
-
if (item.type === "document"
|
|
178
|
-
|
|
158
|
+
/**
|
|
159
|
+
* What `item` takes as new Bausteine of the open file: the file itself (the virtual root, or its document row) takes
|
|
160
|
+
* every kind, a Baustein what its kind accepts as children (`kindAccepts`). Nothing in a flat structure or without an editor.
|
|
161
|
+
*/
|
|
162
|
+
function creatableBelow(item, env) {
|
|
163
|
+
const none = {
|
|
164
|
+
folder: false,
|
|
165
|
+
document: false
|
|
166
|
+
};
|
|
167
|
+
if (env.structure !== "composite" || !env.hasEditor) return none;
|
|
168
|
+
if (item.key === "vroot" || item.type === "document" && item.id === env.activeId) return {
|
|
169
|
+
folder: true,
|
|
170
|
+
document: true
|
|
171
|
+
};
|
|
172
|
+
if (item.type !== "composite" || !item.blockId || !item.kind) return none;
|
|
173
|
+
return {
|
|
174
|
+
folder: kindAccepts(env.kinds, item.kind, "folder"),
|
|
175
|
+
document: kindAccepts(env.kinds, item.kind, "document")
|
|
176
|
+
};
|
|
179
177
|
}
|
|
180
178
|
/** What a dragged item carries: its document or folder, or in virtual mode its composite's block ID. */
|
|
181
179
|
function dragPayloadOf(item) {
|
|
@@ -196,26 +194,7 @@ function isDraggable(item, virtual) {
|
|
|
196
194
|
if (virtual) return item.type === "composite" && !!item.blockId;
|
|
197
195
|
return item.type === "document" || item.type === "folder";
|
|
198
196
|
}
|
|
199
|
-
/**
|
|
200
|
-
* Virtual mode: a composite may be dropped on the document root or a `folder`/`document` composite, but not on itself or
|
|
201
|
-
* inside itself. Otherwise documents and folders may be dropped on the root or a folder, unless that changes nothing or
|
|
202
|
-
* nests a folder in itself.
|
|
203
|
-
*/
|
|
204
|
-
function canDrop(source, target, env) {
|
|
205
|
-
if (!source) return false;
|
|
206
|
-
if (source.type === "composite") {
|
|
207
|
-
const instance = env.editor;
|
|
208
|
-
if (!env.virtual || !instance || instance.isDestroyed) return false;
|
|
209
|
-
if (target.key === "vroot") return canMoveBlockInto(instance, source.blockId, null);
|
|
210
|
-
if (target.type !== "composite" || !target.blockId || target.kind !== "folder" && target.kind !== "document") return false;
|
|
211
|
-
return canMoveBlockInto(instance, source.blockId, target.blockId);
|
|
212
|
-
}
|
|
213
|
-
if (env.virtual || target.type !== "root" && target.type !== "folder") return false;
|
|
214
|
-
const targetPath = target.path;
|
|
215
|
-
if (source.type === "document") return source.path !== targetPath;
|
|
216
|
-
return targetPath !== source.path && !targetPath.startsWith(`${source.path}/`) && parentPath(source.path) !== targetPath;
|
|
217
|
-
}
|
|
218
197
|
//#endregion
|
|
219
|
-
export { DRAG_MIME, EMPTY_OUTLINE, VIRTUAL_ROOT, ancestorKeys, applyNodeFilters,
|
|
198
|
+
export { DRAG_MIME, EMPTY_OUTLINE, VIRTUAL_ROOT, ancestorKeys, applyNodeFilters, collectFolderPaths, collectKeys, creatableBelow, dragPayloadOf, fileKinds, filterKinds, findFolder, folderItem, folderNamesIn, isDraggable, joinPath, outlineItems, parentPath, readOutline, virtualRootItem };
|
|
220
199
|
|
|
221
200
|
//# sourceMappingURL=tree-items.js.map
|