@ecsplico/editor 3.2.0 → 3.4.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/code/CodeSurface.vue.d.ts +3 -3
  3. package/dist/document/DocumentSurface.vue.d.ts +3 -3
  4. package/dist/features/ai/protocol/generate.d.ts +1 -1
  5. package/dist/features/composites/components/CompositeChrome.js +10 -9
  6. package/dist/features/composites/components/CompositeChrome.js.map +1 -1
  7. package/dist/features/composites/components/CompositeView.js +5 -1
  8. package/dist/features/composites/components/CompositeView.js.map +1 -1
  9. package/dist/features/composites/feature.js +12 -1
  10. package/dist/features/composites/feature.js.map +1 -1
  11. package/dist/features/composites/index.d.ts +2 -1
  12. package/dist/features/composites/index.js +2 -1
  13. package/dist/features/composites/kinds.d.ts +10 -1
  14. package/dist/features/composites/kinds.js.map +1 -1
  15. package/dist/features/composites/merge.d.ts +24 -0
  16. package/dist/features/composites/merge.js +50 -0
  17. package/dist/features/composites/merge.js.map +1 -0
  18. package/dist/features/composites/nodes.d.ts +1 -0
  19. package/dist/features/composites/nodes.js +1 -1
  20. package/dist/features/composites/nodes.js.map +1 -1
  21. package/dist/features/composites/outline.d.ts +9 -2
  22. package/dist/features/composites/outline.js +41 -3
  23. package/dist/features/composites/outline.js.map +1 -1
  24. package/dist/features/composites/react/CompositeChrome.js +11 -9
  25. package/dist/features/composites/react/CompositeChrome.js.map +1 -1
  26. package/dist/features/composites/react/CompositeView.js +4 -1
  27. package/dist/features/composites/react/CompositeView.js.map +1 -1
  28. package/dist/features/composites/rows.d.ts +2 -0
  29. package/dist/features/composites/rows.js +1 -0
  30. package/dist/features/composites/rows.js.map +1 -1
  31. package/dist/i18n/de/composites.js +1 -0
  32. package/dist/i18n/de/composites.js.map +1 -1
  33. package/dist/i18n/de/tree.js +1 -0
  34. package/dist/i18n/de/tree.js.map +1 -1
  35. package/dist/i18n/de.d.ts +2 -0
  36. package/dist/i18n/en/composites.js +1 -0
  37. package/dist/i18n/en/composites.js.map +1 -1
  38. package/dist/i18n/en/tree.js +1 -0
  39. package/dist/i18n/en/tree.js.map +1 -1
  40. package/dist/markdown/MarkdownSurface.vue.d.ts +5 -5
  41. package/dist/nuxt/module.d.ts +4 -0
  42. package/dist/server/handlers/ai.d.ts +1 -1
  43. package/dist/styles/composites.css +10 -1
  44. package/dist/tree/DocumentTree.js +25 -9
  45. package/dist/tree/DocumentTree.js.map +1 -1
  46. package/dist/tree/react/DocumentTree.js +14 -2
  47. package/dist/tree/react/DocumentTree.js.map +1 -1
  48. package/dist/tree/tree-host.js +11 -1
  49. package/dist/tree/tree-host.js.map +1 -1
  50. package/dist/tree/tree-items.js +3 -1
  51. package/dist/tree/tree-items.js.map +1 -1
  52. package/dist/tree/tree-model.js +9 -0
  53. package/dist/tree/tree-model.js.map +1 -1
  54. package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
  55. package/dist/vue/SimpleEditor.vue.d.ts +4 -4
  56. package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
  57. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"DocumentTree.js","names":[],"sources":["../../../src/tree/react/DocumentTree.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { type DragEvent, Fragment, type KeyboardEvent, type ReactNode, useRef } from 'react'\nimport { BLOCK_MIME, blockTransferOf, readBlockTransfer, writeBlockTransfer } from '../../core/block-transfer'\nimport { findBlockById } from '../../core/schema/block-ids'\nimport { Icon } from '../../react/Icon'\nimport { useEditorI18n } from '../../react/use-editor-i18n'\nimport { useEditorSettings } from '../../react/settings'\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 { Popover, PopoverContent, PopoverTrigger } from '../../react/ui/popover'\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../react/ui/tooltip'\nimport { cn } from '../../react/ui/utils'\nimport type { DocumentStore } from '../store'\nimport { basketById } from '../../features/composites'\nimport { DRAG_MIME, focusCrumbs, itemBasket, 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 BASKET_OPTION = 'flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm text-foreground transition-colors duration-150 hover:bg-muted focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary'\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 { update } = useEditorSettings()\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 // Mirrors the model's `isVirtual` rule: the file root only takes effect for a composite document that is open. Without\n // it the button is disabled (and says why) instead of letting the setting silently fall back to the folders.\n const canUseFileRoot = state.hasEditor && state.structure === 'composite'\n const { view, creatingFolder } = state\n\n /** The basket filter button needs a live editor that offers baskets. */\n const showBasketFilter = state.hasEditor && state.baskets.length > 0\n /** The chosen baskets the open editor offers (an unknown id never filters, see `TreeModel`). */\n const shownBaskets = state.basketFilter.filter(id => basketById(state.baskets, id))\n /** The breadcrumb of the focus: the document (first crumb, leaves focus) and the Bausteine down to the focused one. */\n const { document: focusDocument, crumbs } = focusCrumbs(state.focusPath)\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 // A Baustein row also carries the block itself, so another surface or tree can take a copy.\n const found = payload.type === 'composite' && editor && !editor.isDestroyed ? findBlockById(editor.state.doc, payload.blockId) : null\n const transfer = found && editor ? blockTransferOf(editor, found.pos) : null\n if (transfer) writeBlockTransfer(event.dataTransfer, transfer)\n event.dataTransfer.effectAllowed = transfer ? 'copyMove' : 'move'\n }\n\n /** A drag of this tree's own rows is running (a foreign drag is not). */\n const ownDrag = !!state.drag && state.drag.source.type !== 'foreign-block'\n\n function onDragOver(event: DragEvent<HTMLDivElement>, item: TreeItem) {\n const foreign = !ownDrag && event.dataTransfer.types.includes(BLOCK_MIME)\n const verdict = foreign ? model.foreignDragOver(item) : 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 ? (foreign ? 'copy' : '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 (ownDrag) {\n event.preventDefault()\n void model.drop(item)\n return\n }\n const payload = readBlockTransfer(event.dataTransfer)\n if (!payload) return\n event.preventDefault()\n void model.dropForeign(item, payload, event.shiftKey, event.clientX, event.clientY)\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 const basket = itemBasket(item, state.baskets)\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 row.dimmed && 'opacity-60',\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 {basket && (\n <span\n className=\"size-2 shrink-0 rounded-full\"\n style={{ backgroundColor: basket.color }}\n title={basket.label}\n aria-label={basket.label}\n role=\"img\"\n data-basket-dot\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 {item.type === 'composite' && item.blockId && (\n <Button size=\"icon-xs\" variant=\"ghost\" title={t('tree.focus.enter')} aria-label={t('tree.focus.enter')} onClick={() => model.focus(item.key)}>\n <Icon name=\"i-lucide-focus\" />\n </Button>\n )}\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=\"flex items-center gap-1\">\n <div className=\"relative min-w-0 flex-1\">\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 {showBasketFilter && (\n // A popover, not a dropdown menu: a menu item cannot show a dot in an arbitrary CSS colour.\n <Popover>\n <PopoverTrigger asChild>\n <Button\n size=\"icon-xs\"\n variant={shownBaskets.length ? 'secondary' : 'ghost'}\n title={t('tree.basketFilter.label')}\n aria-label={t('tree.basketFilter.label')}\n >\n <Icon name=\"i-lucide-paint-bucket\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent side=\"bottom\" align=\"end\" sideOffset={4} className=\"w-44 gap-0 p-1\" onCloseAutoFocus={event => event.preventDefault()}>\n <div role=\"menu\" aria-label={t('tree.basketFilter.label')}>\n {state.baskets.map((basket) => {\n const shown = shownBaskets.includes(basket.id)\n return (\n <button\n key={basket.id}\n type=\"button\"\n role=\"menuitemcheckbox\"\n aria-checked={shown}\n className={BASKET_OPTION}\n onClick={() => model.setBasketShown(basket.id, !shown)}\n >\n <span className=\"size-2.5 shrink-0 rounded-full\" style={{ backgroundColor: basket.color }} />\n <span className=\"truncate\">{basket.label}</span>\n {shown && <Icon name=\"i-lucide-check\" className=\"ml-auto size-3.5 shrink-0\" />}\n </button>\n )\n })}\n {shownBaskets.length > 0 && (\n <button type=\"button\" role=\"menuitem\" className={BASKET_OPTION} onClick={() => model.clearBasketFilter()}>\n <Icon name=\"i-lucide-filter-x\" className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{t('tree.basketFilter.reset')}</span>\n </button>\n )}\n </div>\n </PopoverContent>\n </Popover>\n )}\n </div>\n\n <div className=\"flex items-center justify-end\">\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n {/* A disabled button swallows pointer events, so the tooltip hangs on the wrapper. */}\n <span className=\"inline-flex\">\n <Button\n size=\"xs\"\n variant={state.virtual ? 'secondary' : 'ghost'}\n disabled={!canUseFileRoot}\n aria-pressed={state.virtual}\n onClick={() => update((draft) => { draft.blocks.treeRoot = draft.blocks.treeRoot === 'file' ? 'folders' : 'file' })}\n >\n <Icon name={state.virtual ? 'i-lucide-file-stack' : 'i-lucide-folder-tree'} />\n {t(state.virtual ? 'tree.root.file' : 'tree.root.folders')}\n </Button>\n </span>\n </TooltipTrigger>\n {!canUseFileRoot && <TooltipContent>{t('tree.root.unavailable')}</TooltipContent>}\n </Tooltip>\n </TooltipProvider>\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 {crumbs.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-0.5 text-xs\">\n <Button size=\"xs\" variant=\"ghost\" title={t('tree.focus.leave')} aria-label={t('tree.focus.leave')} onClick={() => model.defocus()}>\n <Icon name=\"i-lucide-corner-up-left\" />\n {focusDocument?.label ?? t('tree.focus.root')}\n </Button>\n {crumbs.map((crumb, index) => (\n <Fragment key={crumb.key}>\n <Icon name=\"i-lucide-chevron-right\" className=\"size-3 shrink-0 text-muted-foreground\" />\n {index === crumbs.length - 1\n ? <span className=\"min-w-0 truncate px-1.5 font-medium\" aria-current=\"location\">{crumb.label}</span>\n : <Button size=\"xs\" variant=\"ghost\" onClick={() => model.focus(crumb.key)}>{crumb.label}</Button>}\n </Fragment>\n ))}\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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"}
1
+ {"version":3,"file":"DocumentTree.js","names":[],"sources":["../../../src/tree/react/DocumentTree.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { type DragEvent, Fragment, type KeyboardEvent, type ReactNode, useRef } from 'react'\nimport { BLOCK_MIME, blockTransferOf, readBlockTransfer, writeBlockTransfer } from '../../core/block-transfer'\nimport { findBlockById } from '../../core/schema/block-ids'\nimport { Icon } from '../../react/Icon'\nimport { useEditorI18n } from '../../react/use-editor-i18n'\nimport { useEditorSettings } from '../../react/settings'\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 { Popover, PopoverContent, PopoverTrigger } from '../../react/ui/popover'\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../react/ui/tooltip'\nimport { cn } from '../../react/ui/utils'\nimport type { DocumentStore } from '../store'\nimport { basketById } from '../../features/composites'\nimport { DRAG_MIME, focusCrumbs, itemBasket, 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 BASKET_OPTION = 'flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm text-foreground transition-colors duration-150 hover:bg-muted focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary'\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 { update } = useEditorSettings()\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 // Mirrors the model's `isVirtual` rule: the file root only takes effect for a composite document that is open. Without\n // it the button is disabled (and says why) instead of letting the setting silently fall back to the folders.\n const canUseFileRoot = state.hasEditor && state.structure === 'composite'\n const { view, creatingFolder } = state\n\n /** The basket filter button needs a live editor that offers baskets. */\n const showBasketFilter = state.hasEditor && state.baskets.length > 0\n /** The chosen baskets the open editor offers (an unknown id never filters, see `TreeModel`). */\n const shownBaskets = state.basketFilter.filter(id => basketById(state.baskets, id))\n /** The breadcrumb of the focus: the document (first crumb, leaves focus) and the Bausteine down to the focused one. */\n const { document: focusDocument, crumbs } = focusCrumbs(state.focusPath)\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 // A Baustein row also carries the block itself, so another surface or tree can take a copy.\n const found = payload.type === 'composite' && editor && !editor.isDestroyed ? findBlockById(editor.state.doc, payload.blockId) : null\n const transfer = found && editor ? blockTransferOf(editor, found.pos) : null\n if (transfer) writeBlockTransfer(event.dataTransfer, transfer)\n event.dataTransfer.effectAllowed = transfer ? 'copyMove' : 'move'\n }\n\n /** A drag of this tree's own rows is running (a foreign drag is not). */\n const ownDrag = !!state.drag && state.drag.source.type !== 'foreign-block'\n\n function onDragOver(event: DragEvent<HTMLDivElement>, item: TreeItem) {\n const foreign = !ownDrag && event.dataTransfer.types.includes(BLOCK_MIME)\n const verdict = foreign ? model.foreignDragOver(item) : 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 ? (foreign ? 'copy' : '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 (ownDrag) {\n event.preventDefault()\n void model.drop(item)\n return\n }\n const payload = readBlockTransfer(event.dataTransfer)\n if (!payload) return\n event.preventDefault()\n void model.dropForeign(item, payload, event.shiftKey, event.clientX, event.clientY)\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 const basket = itemBasket(item, state.baskets)\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 row.dimmed && 'opacity-60',\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} style={item.tone ? { color: item.tone } : undefined} />\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 {basket && (\n <span\n className=\"size-2 shrink-0 rounded-full\"\n style={{ backgroundColor: basket.color }}\n title={basket.label}\n aria-label={basket.label}\n role=\"img\"\n data-basket-dot\n />\n )}\n <span className={cn('min-w-0 flex-1 truncate', item.labelFromBody && 'italic text-muted-foreground')} title={item.label} data-label-from-body={item.labelFromBody || undefined}><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 {item.type === 'composite' && item.blockId && (\n <Button size=\"icon-xs\" variant=\"ghost\" title={t('tree.focus.enter')} aria-label={t('tree.focus.enter')} onClick={() => model.focus(item.key)}>\n <Icon name=\"i-lucide-focus\" />\n </Button>\n )}\n {model.canMerge(item) && (\n <Button size=\"icon-xs\" variant=\"ghost\" title={t('tree.merge')} aria-label={t('tree.merge')} onClick={() => model.merge(item)}>\n <Icon name=\"i-lucide-merge\" />\n </Button>\n )}\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=\"flex items-center gap-1\">\n <div className=\"relative min-w-0 flex-1\">\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 {showBasketFilter && (\n // A popover, not a dropdown menu: a menu item cannot show a dot in an arbitrary CSS colour.\n <Popover>\n <PopoverTrigger asChild>\n <Button\n size=\"icon-xs\"\n variant={shownBaskets.length ? 'secondary' : 'ghost'}\n title={t('tree.basketFilter.label')}\n aria-label={t('tree.basketFilter.label')}\n >\n <Icon name=\"i-lucide-paint-bucket\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent side=\"bottom\" align=\"end\" sideOffset={4} className=\"w-44 gap-0 p-1\" onCloseAutoFocus={event => event.preventDefault()}>\n <div role=\"menu\" aria-label={t('tree.basketFilter.label')}>\n {state.baskets.map((basket) => {\n const shown = shownBaskets.includes(basket.id)\n return (\n <button\n key={basket.id}\n type=\"button\"\n role=\"menuitemcheckbox\"\n aria-checked={shown}\n className={BASKET_OPTION}\n onClick={() => model.setBasketShown(basket.id, !shown)}\n >\n <span className=\"size-2.5 shrink-0 rounded-full\" style={{ backgroundColor: basket.color }} />\n <span className=\"truncate\">{basket.label}</span>\n {shown && <Icon name=\"i-lucide-check\" className=\"ml-auto size-3.5 shrink-0\" />}\n </button>\n )\n })}\n {shownBaskets.length > 0 && (\n <button type=\"button\" role=\"menuitem\" className={BASKET_OPTION} onClick={() => model.clearBasketFilter()}>\n <Icon name=\"i-lucide-filter-x\" className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{t('tree.basketFilter.reset')}</span>\n </button>\n )}\n </div>\n </PopoverContent>\n </Popover>\n )}\n </div>\n\n <div className=\"flex items-center justify-end\">\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n {/* A disabled button swallows pointer events, so the tooltip hangs on the wrapper. */}\n <span className=\"inline-flex\">\n <Button\n size=\"xs\"\n variant={state.virtual ? 'secondary' : 'ghost'}\n disabled={!canUseFileRoot}\n aria-pressed={state.virtual}\n onClick={() => update((draft) => { draft.blocks.treeRoot = draft.blocks.treeRoot === 'file' ? 'folders' : 'file' })}\n >\n <Icon name={state.virtual ? 'i-lucide-file-stack' : 'i-lucide-folder-tree'} />\n {t(state.virtual ? 'tree.root.file' : 'tree.root.folders')}\n </Button>\n </span>\n </TooltipTrigger>\n {!canUseFileRoot && <TooltipContent>{t('tree.root.unavailable')}</TooltipContent>}\n </Tooltip>\n </TooltipProvider>\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 {crumbs.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-0.5 text-xs\">\n <Button size=\"xs\" variant=\"ghost\" title={t('tree.focus.leave')} aria-label={t('tree.focus.leave')} onClick={() => model.defocus()}>\n <Icon name=\"i-lucide-corner-up-left\" />\n {focusDocument?.label ?? t('tree.focus.root')}\n </Button>\n {crumbs.map((crumb, index) => (\n <Fragment key={crumb.key}>\n <Icon name=\"i-lucide-chevron-right\" className=\"size-3 shrink-0 text-muted-foreground\" />\n {index === crumbs.length - 1\n ? <span className=\"min-w-0 truncate px-1.5 font-medium\" aria-current=\"location\">{crumb.label}</span>\n : <Button size=\"xs\" variant=\"ghost\" onClick={() => model.focus(crumb.key)}>{crumb.label}</Button>}\n </Fragment>\n ))}\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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"}
@@ -1,9 +1,9 @@
1
1
  import { findBlockById } from "../core/schema/block-ids.js";
2
2
  import { compositeBasketsOf, compositeJSON, compositeKindsOf } from "../features/composites/nodes.js";
3
- import { revealBlockInEditor } from "../outline/heading-outline.js";
4
3
  import { pickFiles } from "../features/media/upload.js";
5
4
  import { compositeFocusOf } from "../features/composites/focus.js";
6
5
  import { moveBlockInto, moveIntoVerdict } from "../features/composites/outline.js";
6
+ import { revealBlockInEditor } from "../outline/heading-outline.js";
7
7
  import { readOutline } from "./tree-items.js";
8
8
  //#region src/tree/tree-host.ts
9
9
  /** 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. */
@@ -18,6 +18,10 @@ function treeEditorOf(editor, t) {
18
18
  } : moveIntoVerdict(editor, id, targetId, t),
19
19
  canTransfer: () => !editor.isDestroyed && editor.storage.blockDropTarget !== void 0 && typeof editor.commands.transferBlock === "function",
20
20
  focusedId: () => editor.isDestroyed ? null : compositeFocusOf(editor.state),
21
+ canMerge: (id) => {
22
+ const found = editor.isDestroyed ? void 0 : findBlockById(editor.state.doc, id);
23
+ return !!found && editor.can().mergeCompositeWithNext(found.pos);
24
+ },
21
25
  onChange(listener) {
22
26
  let focused = editor.isDestroyed ? null : compositeFocusOf(editor.state);
23
27
  const onTransaction = () => {
@@ -120,6 +124,12 @@ function createTreeExecutor(host) {
120
124
  const editor = liveEditor();
121
125
  return !!editor && editor.commands.focusComposite(command.id);
122
126
  }
127
+ case "mergeBlock": {
128
+ const editor = liveEditor();
129
+ if (!editor) return false;
130
+ const found = findBlockById(editor.state.doc, command.id);
131
+ return !!found && editor.commands.mergeCompositeWithNext(found.pos);
132
+ }
123
133
  }
124
134
  };
125
135
  }
@@ -1 +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 { compositeBasketsOf, compositeFocusOf, 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 baskets: compositeBasketsOf(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 canTransfer: () => !editor.isDestroyed && editor.storage.blockDropTarget !== undefined && typeof editor.commands.transferBlock === 'function',\n focusedId: () => (editor.isDestroyed ? null : compositeFocusOf(editor.state)),\n onChange(listener) {\n // A focus change is a transaction without a document change (the focus chip, a block's „Fokussieren“), so `update` does not see it.\n let focused = editor.isDestroyed ? null : compositeFocusOf(editor.state)\n const onTransaction = () => {\n if (editor.isDestroyed) return\n const now = compositeFocusOf(editor.state)\n if (now === focused) return\n focused = now\n listener()\n }\n editor.on('update', listener)\n editor.on('transaction', onTransaction)\n return () => {\n editor.off('update', listener)\n editor.off('transaction', onTransaction)\n }\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 'transferBlock': {\n const editor = liveEditor()\n // An editor without the transfer feature has no such command: the drop is refused (`false`), never thrown.\n if (!editor || typeof editor.commands.transferBlock !== 'function') return false\n return editor.commands.transferBlock({ payload: command.payload, pos: compositeInsertPos(editor, command.targetId), menu: command.menu, x: command.x, y: command.y })\n }\n case 'warn':\n host.warn(command.title, command.description)\n return true\n case 'focusBlock': {\n const editor = liveEditor()\n return !!editor && editor.commands.focusComposite(command.id)\n }\n }\n }\n}\n"],"mappings":"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"}
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 { compositeBasketsOf, compositeFocusOf, 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 baskets: compositeBasketsOf(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 canTransfer: () => !editor.isDestroyed && editor.storage.blockDropTarget !== undefined && typeof editor.commands.transferBlock === 'function',\n focusedId: () => (editor.isDestroyed ? null : compositeFocusOf(editor.state)),\n canMerge: (id) => {\n const found = editor.isDestroyed ? undefined : findBlockById(editor.state.doc, id)\n return !!found && editor.can().mergeCompositeWithNext(found.pos)\n },\n onChange(listener) {\n // A focus change is a transaction without a document change (the focus chip, a block's „Fokussieren“), so `update` does not see it.\n let focused = editor.isDestroyed ? null : compositeFocusOf(editor.state)\n const onTransaction = () => {\n if (editor.isDestroyed) return\n const now = compositeFocusOf(editor.state)\n if (now === focused) return\n focused = now\n listener()\n }\n editor.on('update', listener)\n editor.on('transaction', onTransaction)\n return () => {\n editor.off('update', listener)\n editor.off('transaction', onTransaction)\n }\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 'transferBlock': {\n const editor = liveEditor()\n // An editor without the transfer feature has no such command: the drop is refused (`false`), never thrown.\n if (!editor || typeof editor.commands.transferBlock !== 'function') return false\n return editor.commands.transferBlock({ payload: command.payload, pos: compositeInsertPos(editor, command.targetId), menu: command.menu, x: command.x, y: command.y })\n }\n case 'warn':\n host.warn(command.title, command.description)\n return true\n case 'focusBlock': {\n const editor = liveEditor()\n return !!editor && editor.commands.focusComposite(command.id)\n }\n case 'mergeBlock': {\n const editor = liveEditor()\n if (!editor) return false\n const found = findBlockById(editor.state.doc, command.id)\n return !!found && editor.commands.mergeCompositeWithNext(found.pos)\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { compositeKind, defaultCompositeKinds, kindAccepts } from "../features/composites/kinds.js";
2
2
  import { basketById } from "../features/composites/baskets.js";
3
- import { headingOutline } from "../outline/heading-outline.js";
4
3
  import { compositeOutline } from "../features/composites/outline.js";
4
+ import { headingOutline } from "../outline/heading-outline.js";
5
5
  import { folderKey } from "./folder-paths.js";
6
6
  //#region src/tree/tree-items.ts
7
7
  /** Key of the virtual-mode root item (the open document). */
@@ -57,12 +57,14 @@ function outlineItems(nodes, kinds, select) {
57
57
  const item = {
58
58
  key: `outline:${node.key}`,
59
59
  label: composite ? node.title : node.text,
60
+ labelFromBody: composite && node.labelFromBody ? true : void 0,
60
61
  icon: composite ? compositeKind(kinds, node.kind).icon : `i-lucide-heading-${node.level}`,
61
62
  type: composite ? "composite" : "heading",
62
63
  blockId: node.id,
63
64
  pos: node.pos,
64
65
  kind: composite ? node.kind : void 0,
65
66
  hasSummary: composite ? node.hasSummary : void 0,
67
+ tone: composite ? compositeKind(kinds, node.kind).chrome?.tone || void 0 : void 0,
66
68
  basket: composite ? node.basket : void 0,
67
69
  children: node.children.length ? outlineItems(node.children, kinds, select) : void 0,
68
70
  onSelect: () => select(item),
@@ -1 +1 @@
1
- {"version":3,"file":"tree-items.js","names":[],"sources":["../../src/tree/tree-items.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { type Basket, basketById, compositeKind, type CompositeKind, compositeOutline, defaultCompositeKinds, kindAccepts, type OutlineNode } from '../features/composites'\nimport type { Translate } from '../i18n'\nimport { type HeadingOutlineNode, headingOutline } from '../outline'\nimport { folderKey } from './folder-paths'\nimport type { TreeFolder } from './types'\nimport type { NodeFilters } from './tree-view-model'\n\n// What the Vue and the React `DocumentTree` share: the items of the file browser (folders and documents of the store,\n// the open document's outline), their kind filters, what can be dragged and created, and the path arithmetic. No\n// framework, no state; the drop rules are `drop-rules.ts`, the text filter and the visible rows `tree-rows.ts`.\n\n/** One row of the file browser: a store folder or document, or an entry of the open document's outline. Which fields are set depends on `type`. */\nexport interface TreeItem {\n /** Unique within the tree: `root`, `folder:<path>`, `doc:<id>`, `outline:<block key>` */\n key: string\n label: string\n icon: string\n type: 'root' | 'folder' | 'document' | 'composite' | 'heading'\n /** Folder path of a root/folder item (`''` = root) */\n path?: string\n /** Document id */\n id?: string\n /** Folder path a document item lives in */\n folder?: string\n /** Outline items: block ID of the composite/heading (`null` while it has none) and its position when the outline was read */\n blockId?: string | null\n pos?: number\n /** Composite items: the composite's kind, and whether it has a summary (description) */\n kind?: string\n hasSummary?: boolean\n /** Composite items: the basket id (`meta.basket`), `null`/absent without one */\n basket?: string | null\n children?: TreeItem[]\n onSelect?: (e?: Event) => void\n onToggle?: (e?: Event) => void\n}\n\n/**\n * What a drag carries (serialized under `DRAG_MIME`): a document or folder of the store, or a composite of the open document.\n * `foreign-block` is a Baustein from another editor surface or document tree (it travels under `BLOCK_MIME`, not `DRAG_MIME`).\n */\nexport type DragPayload\n = | {\n type: 'document' | 'folder'\n /** Document id */\n id?: string\n /** Folder path of a folder item, or the folder a document currently lives in */\n path: string\n }\n /** Virtual mode: a composite of the open document, by block ID */\n | { type: 'composite', blockId: string }\n /** A Baustein dragged in from outside this tree; only its verdict is known until the drop reads the payload. */\n | { type: 'foreign-block' }\n\n/** Key of the virtual-mode root item (the open document). */\nexport const VIRTUAL_ROOT = 'vroot'\n\nexport const DRAG_MIME = 'application/x-tip-tree'\n\nexport const parentPath = (folder: string) => folder.slice(0, Math.max(folder.lastIndexOf('/'), 0))\nexport const joinPath = (folder: string, name: string) => (folder ? `${folder}/${name}` : name)\n\n/** Item keys from the root down to `folder`. */\nexport function ancestorKeys(folder: string): string[] {\n const keys = ['root']\n let path = ''\n for (const segment of folder.split('/').filter(Boolean)) {\n path = joinPath(path, segment)\n keys.push(folderKey(path))\n }\n return keys\n}\n\n/** The folder at `path` below (or at) `from`; undefined when there is none. */\nexport function findFolder(path: string, from: TreeFolder | null | undefined): TreeFolder | undefined {\n if (!from) return undefined\n if (from.path === path) return from\n for (const sub of from.folders) {\n const found = path === sub.path || path.startsWith(`${sub.path}/`) ? findFolder(path, sub) : undefined\n if (found) return found\n }\n return undefined\n}\n\n/** Names of the folders directly in `parent`, without the folder at path `except`. */\nexport const folderNamesIn = (tree: TreeFolder | null | undefined, parent: string, except?: string) =>\n (findFolder(parent, tree)?.folders ?? []).filter(sub => sub.path !== except).map(sub => sub.name)\n\nexport function collectFolderPaths(folder: TreeFolder, into: Set<string>) {\n into.add(folder.path)\n folder.folders.forEach(sub => collectFolderPaths(sub, into))\n}\n\n// --- outline of the open document ------------------------------------------------------------------------------\n\nexport type Outline = { structure: 'composite', nodes: OutlineNode[] } | { structure: 'flat', nodes: HeadingOutlineNode[] }\n\nexport const EMPTY_OUTLINE: Outline = { structure: 'composite', nodes: [] }\n\n/** The outline of the editor's document: the composites in composite structure, else the headings. */\nexport function readOutline(editor: Editor | undefined, structure: string, t: Translate): Outline {\n if (!editor || editor.isDestroyed) return EMPTY_OUTLINE\n const { doc } = editor.state\n return structure === 'composite'\n ? { structure: 'composite', nodes: compositeOutline(doc, new Set(), t('tree.untitled')) }\n : { structure: 'flat', nodes: headingOutline(doc, t('tree.untitled')) }\n}\n\n/** The outline entries as items; `select` runs when one is chosen. */\nexport function outlineItems(nodes: readonly (OutlineNode | HeadingOutlineNode)[], kinds: readonly CompositeKind[], select: (item: TreeItem) => void): TreeItem[] {\n return nodes.map((node) => {\n const composite = 'kind' in node\n const item: TreeItem = {\n key: `outline:${node.key}`,\n label: composite ? node.title : node.text,\n icon: composite ? compositeKind(kinds, node.kind).icon : `i-lucide-heading-${node.level}`,\n type: composite ? 'composite' : 'heading',\n blockId: node.id,\n pos: node.pos,\n kind: composite ? node.kind : undefined,\n hasSummary: composite ? node.hasSummary : undefined,\n basket: composite ? node.basket : undefined,\n children: node.children.length ? outlineItems(node.children as (OutlineNode | HeadingOutlineNode)[], kinds, select) : undefined,\n onSelect: () => select(item),\n onToggle: e => e?.preventDefault(), // the chevron toggles, a click reveals\n }\n return item\n })\n}\n\n/** The basket of a composite item among `baskets`; `undefined` without one (or when the host no longer offers it). */\nexport const itemBasket = (item: TreeItem, baskets: readonly Basket[]): Basket | undefined => basketById(baskets, item.basket)\n\n// --- items -----------------------------------------------------------------------------------------------------\n\n/** The store's folders and documents as items; the open document (`activeId`) lists `outline` below it. */\nexport function folderItem(folder: TreeFolder, activeId: string | undefined, outline: TreeItem[], select: (item: TreeItem) => void): TreeItem {\n const root = folder.path === ''\n const item: TreeItem = {\n key: root ? 'root' : folderKey(folder.path),\n label: folder.name,\n icon: root ? 'i-lucide-hard-drive' : 'i-lucide-folder',\n type: root ? 'root' : 'folder',\n path: folder.path,\n children: [\n ...folder.folders.map(sub => folderItem(sub, activeId, outline, select)),\n ...folder.documents.map((doc) => {\n const document: TreeItem = {\n key: `doc:${doc.id}`,\n label: doc.title,\n icon: 'i-lucide-file',\n type: 'document',\n id: doc.id,\n folder: doc.folder,\n // The open document lists its outline; its chevron (not a row click) opens and closes it.\n children: doc.id === activeId && outline.length ? outline : undefined,\n onSelect: () => select(document),\n onToggle: e => e?.preventDefault(),\n }\n return document\n }),\n ],\n onSelect: () => select(item),\n }\n return item\n}\n\n/** Virtual mode: the open document is the root, its `folder`/`document` composites (and all others) the tree below it. */\nexport function virtualRootItem(label: string, outline: TreeItem[], select: (item: TreeItem) => void): TreeItem {\n const item: TreeItem = {\n key: VIRTUAL_ROOT,\n label,\n icon: 'i-lucide-package',\n type: 'root',\n children: outline.length ? outline : undefined,\n onSelect: () => select(item),\n }\n return item\n}\n\n/** An item of the unfiltered tree and the key of its parent (`null` for a top-level item). */\nexport interface IndexedItem {\n item: TreeItem\n parent: string | null\n}\n\n/** Adds `list` and all items below it to `into` by key; returns `into`. */\nexport function indexItems(list: readonly TreeItem[], into: Map<string, IndexedItem>, parent: string | null = null) {\n for (const item of list) {\n into.set(item.key, { item, parent })\n if (item.children) indexItems(item.children, into, item.key)\n }\n return into\n}\n\n/** The items from the top-level one down to the item at `key` (empty when `key` is not in `index`). */\nexport function pathTo(index: ReadonlyMap<string, IndexedItem>, key: string): TreeItem[] {\n const path: TreeItem[] = []\n let entry = index.get(key)\n if (!entry) return path\n for (;;) {\n path.unshift(entry.item)\n if (entry.parent === null) return path\n entry = index.get(entry.parent)!\n }\n}\n\n/**\n * The breadcrumb of a focus (`TreeState.focusPath`): `document` is the open document's own row (the virtual root, or the\n * document row of the store; its label words the first crumb, which leaves focus), `crumbs` the Bausteine from there\n * down to the focused one, which is last.\n */\nexport function focusCrumbs(path: readonly TreeItem[]): { document: TreeItem | undefined, crumbs: TreeItem[] } {\n return {\n document: path.find(item => item.type === 'document' || item.key === VIRTUAL_ROOT),\n crumbs: path.filter(item => item.type === 'composite'),\n }\n}\n\n// --- filters ---------------------------------------------------------------------------------------------------\n\nconst fileKindsOf = new WeakMap<Translate, CompositeKind[]>()\n\n/** The kinds a folder lists: Ordner and Dokumente (in the language of `t`; one list per translator). */\nexport function fileKinds(t: Translate): CompositeKind[] {\n let kinds = fileKindsOf.get(t)\n if (!kinds) {\n const builtIn = defaultCompositeKinds(t)\n kinds = ['folder', 'document'].map(id => compositeKind(builtIn, id))\n fileKindsOf.set(t, kinds)\n }\n return kinds\n}\n\n/** What an item is filtered by: the composite's kind, or Ordner/Dokument for the store's folders and documents. */\nfunction itemKind(item: TreeItem): string | undefined {\n return item.kind ?? (item.type === 'folder' || item.type === 'document' ? item.type : undefined)\n}\n\n/** The kinds `item`'s filter can hide below it (`kinds` = those of the open editor). */\nexport function filterKinds(item: TreeItem, env: { structure: string, activeId: string | undefined, kinds: readonly CompositeKind[], t: Translate }): readonly CompositeKind[] {\n const composite = env.structure === 'composite'\n if (item.key === VIRTUAL_ROOT || item.type === 'composite') return env.kinds\n if (item.type === 'document') return composite && item.id === env.activeId ? env.kinds : []\n if (item.type === 'root' || item.type === 'folder') return fileKinds(env.t)\n return []\n}\n\n/**\n * Drops the items of kinds an ancestor's filter hides. Like the outline's own kind filter, their visible descendants\n * move up into the parent's list. A node's own filter applies to everything below it.\n */\nexport function applyNodeFilters(list: readonly TreeItem[], filters: NodeFilters, inherited: ReadonlySet<string> = new Set()): TreeItem[] {\n return list.flatMap((item) => {\n const own = filters[item.key]\n const children = item.children ? applyNodeFilters(item.children, filters, own?.length ? new Set([...inherited, ...own]) : inherited) : undefined\n const kind = itemKind(item)\n if (kind && inherited.has(kind)) return children ?? []\n return [{ ...item, children: children?.length ? children : undefined }]\n })\n}\n\n/**\n * Hides the composites that are not in a `shown` basket and have no visible descendant; ancestors that stay only\n * because of a visible descendant are collected in `dimmed`. Documents, folders, headings and the roots are never hidden.\n */\nexport function applyBasketFilter(list: readonly TreeItem[], shown: ReadonlySet<string>, dimmed: Set<string>): TreeItem[] {\n return list.flatMap((item) => {\n const children = item.children ? applyBasketFilter(item.children, shown, dimmed) : undefined\n if (item.type === 'composite') {\n const matches = !!item.basket && shown.has(item.basket)\n if (!matches && !children?.length) return []\n if (!matches) dimmed.add(item.key)\n }\n return [{ ...item, children: children?.length ? children : undefined }]\n })\n}\n\n// --- creating ----------------------------------------------------------------------------------------------------\n\n/** What can be created below an item of the open file: Ordner-Bausteine, imported Dokument-Bausteine. */\nexport interface Creatable {\n folder: boolean\n document: boolean\n}\n\n/**\n * What `item` takes as new Bausteine of the open file: the file itself (the virtual root, or its document row) takes\n * every kind, a Baustein what its kind accepts as children (`kindAccepts`). Nothing in a flat structure or without an editor.\n */\nexport function creatableBelow(item: TreeItem, env: { structure: string, hasEditor: boolean, activeId: string | undefined, kinds: readonly CompositeKind[] }): Creatable {\n const none: Creatable = { folder: false, document: false }\n if (env.structure !== 'composite' || !env.hasEditor) return none\n if (item.key === VIRTUAL_ROOT || (item.type === 'document' && item.id === env.activeId)) return { folder: true, document: true }\n if (item.type !== 'composite' || !item.blockId || !item.kind) return none\n return { folder: kindAccepts(env.kinds, item.kind, 'folder'), document: kindAccepts(env.kinds, item.kind, 'document') }\n}\n\n// --- drag & drop -----------------------------------------------------------------------------------------------\n\n/** What a dragged item carries: its document or folder, or in virtual mode its composite's block ID. */\nexport function dragPayloadOf(item: TreeItem): DragPayload {\n return item.type === 'composite'\n ? { type: 'composite', blockId: item.blockId! }\n : item.type === 'document'\n ? { type: 'document', id: item.id, path: item.folder ?? '' }\n : { type: 'folder', path: item.path! }\n}\n\n/** Whether `item` may be dragged. Virtual mode moves composites of the open document; blocks without an ID cannot be addressed. */\nexport function isDraggable(item: TreeItem, virtual: boolean): boolean {\n if (virtual) return item.type === 'composite' && !!item.blockId\n return item.type === 'document' || item.type === 'folder'\n}\n\n"],"mappings":"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"}
1
+ {"version":3,"file":"tree-items.js","names":[],"sources":["../../src/tree/tree-items.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { type Basket, basketById, compositeKind, type CompositeKind, compositeOutline, defaultCompositeKinds, kindAccepts, type OutlineNode } from '../features/composites'\nimport type { Translate } from '../i18n'\nimport { type HeadingOutlineNode, headingOutline } from '../outline'\nimport { folderKey } from './folder-paths'\nimport type { TreeFolder } from './types'\nimport type { NodeFilters } from './tree-view-model'\n\n// What the Vue and the React `DocumentTree` share: the items of the file browser (folders and documents of the store,\n// the open document's outline), their kind filters, what can be dragged and created, and the path arithmetic. No\n// framework, no state; the drop rules are `drop-rules.ts`, the text filter and the visible rows `tree-rows.ts`.\n\n/** One row of the file browser: a store folder or document, or an entry of the open document's outline. Which fields are set depends on `type`. */\nexport interface TreeItem {\n /** Unique within the tree: `root`, `folder:<path>`, `doc:<id>`, `outline:<block key>` */\n key: string\n label: string\n /** Composite items: `label` is derived from the body (the Baustein has no title); rows show it muted and italic */\n labelFromBody?: boolean\n icon: string\n type: 'root' | 'folder' | 'document' | 'composite' | 'heading'\n /** Folder path of a root/folder item (`''` = root) */\n path?: string\n /** Document id */\n id?: string\n /** Folder path a document item lives in */\n folder?: string\n /** Outline items: block ID of the composite/heading (`null` while it has none) and its position when the outline was read */\n blockId?: string | null\n pos?: number\n /** Composite items: the composite's kind, and whether it has a summary (description) */\n kind?: string\n hasSummary?: boolean\n /** Composite items: the tone of the kind (`CompositeKind.chrome.tone`) that tints the kind icon; absent for a kind without one */\n tone?: string\n /** Composite items: the basket id (`meta.basket`), `null`/absent without one */\n basket?: string | null\n children?: TreeItem[]\n onSelect?: (e?: Event) => void\n onToggle?: (e?: Event) => void\n}\n\n/**\n * What a drag carries (serialized under `DRAG_MIME`): a document or folder of the store, or a composite of the open document.\n * `foreign-block` is a Baustein from another editor surface or document tree (it travels under `BLOCK_MIME`, not `DRAG_MIME`).\n */\nexport type DragPayload\n = | {\n type: 'document' | 'folder'\n /** Document id */\n id?: string\n /** Folder path of a folder item, or the folder a document currently lives in */\n path: string\n }\n /** Virtual mode: a composite of the open document, by block ID */\n | { type: 'composite', blockId: string }\n /** A Baustein dragged in from outside this tree; only its verdict is known until the drop reads the payload. */\n | { type: 'foreign-block' }\n\n/** Key of the virtual-mode root item (the open document). */\nexport const VIRTUAL_ROOT = 'vroot'\n\nexport const DRAG_MIME = 'application/x-tip-tree'\n\nexport const parentPath = (folder: string) => folder.slice(0, Math.max(folder.lastIndexOf('/'), 0))\nexport const joinPath = (folder: string, name: string) => (folder ? `${folder}/${name}` : name)\n\n/** Item keys from the root down to `folder`. */\nexport function ancestorKeys(folder: string): string[] {\n const keys = ['root']\n let path = ''\n for (const segment of folder.split('/').filter(Boolean)) {\n path = joinPath(path, segment)\n keys.push(folderKey(path))\n }\n return keys\n}\n\n/** The folder at `path` below (or at) `from`; undefined when there is none. */\nexport function findFolder(path: string, from: TreeFolder | null | undefined): TreeFolder | undefined {\n if (!from) return undefined\n if (from.path === path) return from\n for (const sub of from.folders) {\n const found = path === sub.path || path.startsWith(`${sub.path}/`) ? findFolder(path, sub) : undefined\n if (found) return found\n }\n return undefined\n}\n\n/** Names of the folders directly in `parent`, without the folder at path `except`. */\nexport const folderNamesIn = (tree: TreeFolder | null | undefined, parent: string, except?: string) =>\n (findFolder(parent, tree)?.folders ?? []).filter(sub => sub.path !== except).map(sub => sub.name)\n\nexport function collectFolderPaths(folder: TreeFolder, into: Set<string>) {\n into.add(folder.path)\n folder.folders.forEach(sub => collectFolderPaths(sub, into))\n}\n\n// --- outline of the open document ------------------------------------------------------------------------------\n\nexport type Outline = { structure: 'composite', nodes: OutlineNode[] } | { structure: 'flat', nodes: HeadingOutlineNode[] }\n\nexport const EMPTY_OUTLINE: Outline = { structure: 'composite', nodes: [] }\n\n/** The outline of the editor's document: the composites in composite structure, else the headings. */\nexport function readOutline(editor: Editor | undefined, structure: string, t: Translate): Outline {\n if (!editor || editor.isDestroyed) return EMPTY_OUTLINE\n const { doc } = editor.state\n return structure === 'composite'\n ? { structure: 'composite', nodes: compositeOutline(doc, new Set(), t('tree.untitled')) }\n : { structure: 'flat', nodes: headingOutline(doc, t('tree.untitled')) }\n}\n\n/** The outline entries as items; `select` runs when one is chosen. */\nexport function outlineItems(nodes: readonly (OutlineNode | HeadingOutlineNode)[], kinds: readonly CompositeKind[], select: (item: TreeItem) => void): TreeItem[] {\n return nodes.map((node) => {\n const composite = 'kind' in node\n const item: TreeItem = {\n key: `outline:${node.key}`,\n label: composite ? node.title : node.text,\n labelFromBody: composite && node.labelFromBody ? true : undefined,\n icon: composite ? compositeKind(kinds, node.kind).icon : `i-lucide-heading-${node.level}`,\n type: composite ? 'composite' : 'heading',\n blockId: node.id,\n pos: node.pos,\n kind: composite ? node.kind : undefined,\n hasSummary: composite ? node.hasSummary : undefined,\n tone: composite ? compositeKind(kinds, node.kind).chrome?.tone || undefined : undefined,\n basket: composite ? node.basket : undefined,\n children: node.children.length ? outlineItems(node.children as (OutlineNode | HeadingOutlineNode)[], kinds, select) : undefined,\n onSelect: () => select(item),\n onToggle: e => e?.preventDefault(), // the chevron toggles, a click reveals\n }\n return item\n })\n}\n\n/** The basket of a composite item among `baskets`; `undefined` without one (or when the host no longer offers it). */\nexport const itemBasket = (item: TreeItem, baskets: readonly Basket[]): Basket | undefined => basketById(baskets, item.basket)\n\n// --- items -----------------------------------------------------------------------------------------------------\n\n/** The store's folders and documents as items; the open document (`activeId`) lists `outline` below it. */\nexport function folderItem(folder: TreeFolder, activeId: string | undefined, outline: TreeItem[], select: (item: TreeItem) => void): TreeItem {\n const root = folder.path === ''\n const item: TreeItem = {\n key: root ? 'root' : folderKey(folder.path),\n label: folder.name,\n icon: root ? 'i-lucide-hard-drive' : 'i-lucide-folder',\n type: root ? 'root' : 'folder',\n path: folder.path,\n children: [\n ...folder.folders.map(sub => folderItem(sub, activeId, outline, select)),\n ...folder.documents.map((doc) => {\n const document: TreeItem = {\n key: `doc:${doc.id}`,\n label: doc.title,\n icon: 'i-lucide-file',\n type: 'document',\n id: doc.id,\n folder: doc.folder,\n // The open document lists its outline; its chevron (not a row click) opens and closes it.\n children: doc.id === activeId && outline.length ? outline : undefined,\n onSelect: () => select(document),\n onToggle: e => e?.preventDefault(),\n }\n return document\n }),\n ],\n onSelect: () => select(item),\n }\n return item\n}\n\n/** Virtual mode: the open document is the root, its `folder`/`document` composites (and all others) the tree below it. */\nexport function virtualRootItem(label: string, outline: TreeItem[], select: (item: TreeItem) => void): TreeItem {\n const item: TreeItem = {\n key: VIRTUAL_ROOT,\n label,\n icon: 'i-lucide-package',\n type: 'root',\n children: outline.length ? outline : undefined,\n onSelect: () => select(item),\n }\n return item\n}\n\n/** An item of the unfiltered tree and the key of its parent (`null` for a top-level item). */\nexport interface IndexedItem {\n item: TreeItem\n parent: string | null\n}\n\n/** Adds `list` and all items below it to `into` by key; returns `into`. */\nexport function indexItems(list: readonly TreeItem[], into: Map<string, IndexedItem>, parent: string | null = null) {\n for (const item of list) {\n into.set(item.key, { item, parent })\n if (item.children) indexItems(item.children, into, item.key)\n }\n return into\n}\n\n/** The items from the top-level one down to the item at `key` (empty when `key` is not in `index`). */\nexport function pathTo(index: ReadonlyMap<string, IndexedItem>, key: string): TreeItem[] {\n const path: TreeItem[] = []\n let entry = index.get(key)\n if (!entry) return path\n for (;;) {\n path.unshift(entry.item)\n if (entry.parent === null) return path\n entry = index.get(entry.parent)!\n }\n}\n\n/**\n * The breadcrumb of a focus (`TreeState.focusPath`): `document` is the open document's own row (the virtual root, or the\n * document row of the store; its label words the first crumb, which leaves focus), `crumbs` the Bausteine from there\n * down to the focused one, which is last.\n */\nexport function focusCrumbs(path: readonly TreeItem[]): { document: TreeItem | undefined, crumbs: TreeItem[] } {\n return {\n document: path.find(item => item.type === 'document' || item.key === VIRTUAL_ROOT),\n crumbs: path.filter(item => item.type === 'composite'),\n }\n}\n\n// --- filters ---------------------------------------------------------------------------------------------------\n\nconst fileKindsOf = new WeakMap<Translate, CompositeKind[]>()\n\n/** The kinds a folder lists: Ordner and Dokumente (in the language of `t`; one list per translator). */\nexport function fileKinds(t: Translate): CompositeKind[] {\n let kinds = fileKindsOf.get(t)\n if (!kinds) {\n const builtIn = defaultCompositeKinds(t)\n kinds = ['folder', 'document'].map(id => compositeKind(builtIn, id))\n fileKindsOf.set(t, kinds)\n }\n return kinds\n}\n\n/** What an item is filtered by: the composite's kind, or Ordner/Dokument for the store's folders and documents. */\nfunction itemKind(item: TreeItem): string | undefined {\n return item.kind ?? (item.type === 'folder' || item.type === 'document' ? item.type : undefined)\n}\n\n/** The kinds `item`'s filter can hide below it (`kinds` = those of the open editor). */\nexport function filterKinds(item: TreeItem, env: { structure: string, activeId: string | undefined, kinds: readonly CompositeKind[], t: Translate }): readonly CompositeKind[] {\n const composite = env.structure === 'composite'\n if (item.key === VIRTUAL_ROOT || item.type === 'composite') return env.kinds\n if (item.type === 'document') return composite && item.id === env.activeId ? env.kinds : []\n if (item.type === 'root' || item.type === 'folder') return fileKinds(env.t)\n return []\n}\n\n/**\n * Drops the items of kinds an ancestor's filter hides. Like the outline's own kind filter, their visible descendants\n * move up into the parent's list. A node's own filter applies to everything below it.\n */\nexport function applyNodeFilters(list: readonly TreeItem[], filters: NodeFilters, inherited: ReadonlySet<string> = new Set()): TreeItem[] {\n return list.flatMap((item) => {\n const own = filters[item.key]\n const children = item.children ? applyNodeFilters(item.children, filters, own?.length ? new Set([...inherited, ...own]) : inherited) : undefined\n const kind = itemKind(item)\n if (kind && inherited.has(kind)) return children ?? []\n return [{ ...item, children: children?.length ? children : undefined }]\n })\n}\n\n/**\n * Hides the composites that are not in a `shown` basket and have no visible descendant; ancestors that stay only\n * because of a visible descendant are collected in `dimmed`. Documents, folders, headings and the roots are never hidden.\n */\nexport function applyBasketFilter(list: readonly TreeItem[], shown: ReadonlySet<string>, dimmed: Set<string>): TreeItem[] {\n return list.flatMap((item) => {\n const children = item.children ? applyBasketFilter(item.children, shown, dimmed) : undefined\n if (item.type === 'composite') {\n const matches = !!item.basket && shown.has(item.basket)\n if (!matches && !children?.length) return []\n if (!matches) dimmed.add(item.key)\n }\n return [{ ...item, children: children?.length ? children : undefined }]\n })\n}\n\n// --- creating ----------------------------------------------------------------------------------------------------\n\n/** What can be created below an item of the open file: Ordner-Bausteine, imported Dokument-Bausteine. */\nexport interface Creatable {\n folder: boolean\n document: boolean\n}\n\n/**\n * What `item` takes as new Bausteine of the open file: the file itself (the virtual root, or its document row) takes\n * every kind, a Baustein what its kind accepts as children (`kindAccepts`). Nothing in a flat structure or without an editor.\n */\nexport function creatableBelow(item: TreeItem, env: { structure: string, hasEditor: boolean, activeId: string | undefined, kinds: readonly CompositeKind[] }): Creatable {\n const none: Creatable = { folder: false, document: false }\n if (env.structure !== 'composite' || !env.hasEditor) return none\n if (item.key === VIRTUAL_ROOT || (item.type === 'document' && item.id === env.activeId)) return { folder: true, document: true }\n if (item.type !== 'composite' || !item.blockId || !item.kind) return none\n return { folder: kindAccepts(env.kinds, item.kind, 'folder'), document: kindAccepts(env.kinds, item.kind, 'document') }\n}\n\n// --- drag & drop -----------------------------------------------------------------------------------------------\n\n/** What a dragged item carries: its document or folder, or in virtual mode its composite's block ID. */\nexport function dragPayloadOf(item: TreeItem): DragPayload {\n return item.type === 'composite'\n ? { type: 'composite', blockId: item.blockId! }\n : item.type === 'document'\n ? { type: 'document', id: item.id, path: item.folder ?? '' }\n : { type: 'folder', path: item.path! }\n}\n\n/** Whether `item` may be dragged. Virtual mode moves composites of the open document; blocks without an ID cannot be addressed. */\nexport function isDraggable(item: TreeItem, virtual: boolean): boolean {\n if (virtual) return item.type === 'composite' && !!item.blockId\n return item.type === 'document' || item.type === 'folder'\n}\n\n"],"mappings":"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"}
@@ -349,6 +349,13 @@ function createTreeModel(options) {
349
349
  id: null
350
350
  });
351
351
  }
352
+ const canMerge = (item) => item.type === "composite" && !!item.blockId && !!editor?.canMerge(item.blockId);
353
+ function merge(item) {
354
+ if (canMerge(item)) execute({
355
+ type: "mergeBlock",
356
+ id: item.blockId
357
+ });
358
+ }
352
359
  function setSelectedFolder(path) {
353
360
  patch({ selectedFolder: path });
354
361
  }
@@ -624,6 +631,8 @@ function createTreeModel(options) {
624
631
  clearBasketFilter: () => shared.clearBasketFilter(),
625
632
  focus,
626
633
  defocus,
634
+ canMerge,
635
+ merge,
627
636
  select: selectItem,
628
637
  activate(item) {
629
638
  selectItem(item);