@ecsplico/editor 3.3.0 → 3.5.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 (94) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/core/abilities.js +1 -0
  3. package/dist/core/abilities.js.map +1 -1
  4. package/dist/core/backend-capability.d.ts +1 -1
  5. package/dist/core/backend-capability.js +2 -1
  6. package/dist/core/backend-capability.js.map +1 -1
  7. package/dist/core/backend-types.d.ts +25 -0
  8. package/dist/core/backend-types.js.map +1 -1
  9. package/dist/core/backend.d.ts +9 -1
  10. package/dist/core/backend.js.map +1 -1
  11. package/dist/core/http-backend.js +17 -0
  12. package/dist/core/http-backend.js.map +1 -1
  13. package/dist/core/schema/abilities.d.ts +1 -0
  14. package/dist/core/schema/abilities.js +4 -0
  15. package/dist/core/schema/abilities.js.map +1 -1
  16. package/dist/features/ai/protocol/generate.d.ts +1 -1
  17. package/dist/features/composites/components/CompositeChrome.js +10 -9
  18. package/dist/features/composites/components/CompositeChrome.js.map +1 -1
  19. package/dist/features/composites/kinds.d.ts +4 -0
  20. package/dist/features/composites/kinds.js.map +1 -1
  21. package/dist/features/composites/merge.d.ts +1 -1
  22. package/dist/features/composites/merge.js +5 -2
  23. package/dist/features/composites/merge.js.map +1 -1
  24. package/dist/features/composites/nodes.js +1 -0
  25. package/dist/features/composites/nodes.js.map +1 -1
  26. package/dist/features/composites/outline.d.ts +9 -2
  27. package/dist/features/composites/outline.js +41 -3
  28. package/dist/features/composites/outline.js.map +1 -1
  29. package/dist/features/composites/react/CompositeChrome.js +11 -9
  30. package/dist/features/composites/react/CompositeChrome.js.map +1 -1
  31. package/dist/features/composites/rows.d.ts +2 -0
  32. package/dist/features/composites/rows.js +1 -0
  33. package/dist/features/composites/rows.js.map +1 -1
  34. package/dist/features/image-studio/components/ImageStudioChat.js +121 -0
  35. package/dist/features/image-studio/components/ImageStudioChat.js.map +1 -0
  36. package/dist/features/image-studio/components/ImageStudioDialog.js +371 -0
  37. package/dist/features/image-studio/components/ImageStudioDialog.js.map +1 -0
  38. package/dist/features/image-studio/components/ImageStudioDialog.vue.d.ts +25 -0
  39. package/dist/features/image-studio/components/ImageStudioTile.js +74 -0
  40. package/dist/features/image-studio/components/ImageStudioTile.js.map +1 -0
  41. package/dist/features/image-studio/components/ImageStudioView.js +117 -0
  42. package/dist/features/image-studio/components/ImageStudioView.js.map +1 -0
  43. package/dist/features/image-studio/feature.d.ts +24 -0
  44. package/dist/features/image-studio/feature.js +53 -0
  45. package/dist/features/image-studio/feature.js.map +1 -0
  46. package/dist/features/image-studio/index.d.ts +5 -0
  47. package/dist/features/image-studio/index.js +4 -0
  48. package/dist/features/image-studio/node-store.d.ts +27 -0
  49. package/dist/features/image-studio/node-store.js +64 -0
  50. package/dist/features/image-studio/node-store.js.map +1 -0
  51. package/dist/features/image-studio/schema.d.ts +29 -0
  52. package/dist/features/image-studio/schema.js +86 -0
  53. package/dist/features/image-studio/schema.js.map +1 -0
  54. package/dist/features/image-studio/state.d.ts +52 -0
  55. package/dist/features/image-studio/state.js +147 -0
  56. package/dist/features/image-studio/state.js.map +1 -0
  57. package/dist/features/image-studio/store.d.ts +29 -0
  58. package/dist/features/image-studio/vue.d.ts +17 -0
  59. package/dist/features/image-studio/vue.js +20 -0
  60. package/dist/features/image-studio/vue.js.map +1 -0
  61. package/dist/features/media/schema.d.ts +5 -1
  62. package/dist/features/media/schema.js +3 -3
  63. package/dist/features/media/schema.js.map +1 -1
  64. package/dist/features/transcript/segment-node.js +1 -0
  65. package/dist/features/transcript/segment-node.js.map +1 -1
  66. package/dist/i18n/de/image-studio.js +39 -0
  67. package/dist/i18n/de/image-studio.js.map +1 -0
  68. package/dist/i18n/de.d.ts +32 -0
  69. package/dist/i18n/de.js +13 -11
  70. package/dist/i18n/de.js.map +1 -1
  71. package/dist/i18n/en/image-studio.js +39 -0
  72. package/dist/i18n/en/image-studio.js.map +1 -0
  73. package/dist/i18n/en.js +13 -11
  74. package/dist/i18n/en.js.map +1 -1
  75. package/dist/nuxt/module.d.ts +64 -0
  76. package/dist/server/handlers/images.d.ts +22 -0
  77. package/dist/server/handlers/images.js +46 -0
  78. package/dist/server/handlers/images.js.map +1 -0
  79. package/dist/server/handlers/index.d.ts +2 -1
  80. package/dist/server/handlers/index.js +2 -1
  81. package/dist/server/index.d.ts +2 -2
  82. package/dist/server/ports.d.ts +11 -1
  83. package/dist/server/ports.js +2 -1
  84. package/dist/server/ports.js.map +1 -1
  85. package/dist/server/routes.js +19 -0
  86. package/dist/server/routes.js.map +1 -1
  87. package/dist/tree/DocumentTree.js +5 -4
  88. package/dist/tree/DocumentTree.js.map +1 -1
  89. package/dist/tree/react/DocumentTree.js +2 -1
  90. package/dist/tree/react/DocumentTree.js.map +1 -1
  91. package/dist/tree/tree-items.js +1 -0
  92. package/dist/tree/tree-items.js.map +1 -1
  93. package/dist/vue/AdvancedEditor.vue.d.ts +1 -1
  94. package/package.json +16 -1
@@ -0,0 +1,46 @@
1
+ import { HttpError } from "../errors.js";
2
+ import { ASSET_DOCUMENT_ID } from "../../features/media/assets.js";
3
+ import { MAX_MEDIA_BYTES, mediaKindOf } from "../../features/media/schema.js";
4
+ import { authenticated, json, port, readBody } from "./context.js";
5
+ import { z } from "zod";
6
+ //#region src/server/handlers/images.ts
7
+ /** The Prompt-Bausteine the host manages (`ImageStudioPromptBlock[]`). `501` without an `images` port. */
8
+ async function imagePromptBlocksGet(ctx, request) {
9
+ const user = await authenticated(ctx, request);
10
+ return json(await port(ctx, "images").promptBlocks(user));
11
+ }
12
+ /** Body schema of `imageGeneratePost`. */
13
+ const imageGenerateBodySchema = z.object({
14
+ description: z.string().max(1e4),
15
+ instruction: z.string().max(4e3),
16
+ promptBlockIds: z.array(z.string().min(1).max(200)).max(50),
17
+ referenceUrls: z.array(z.string().min(1).max(2048)).max(20)
18
+ });
19
+ /** Generates one image for the body (`ImageStudioGenerateBody`); answers the stored `ImageStudioImage`. `400` for a malformed body, `501` without an `images` port. */
20
+ async function imageGeneratePost(ctx, request) {
21
+ const user = await authenticated(ctx, request);
22
+ const images = port(ctx, "images");
23
+ const body = await readBody(request, imageGenerateBodySchema);
24
+ return json(await images.generate(body, user));
25
+ }
26
+ /**
27
+ * Stores a reference image: multipart fields `documentId` (the document it belongs to, or `scratch`) and `file` (an image);
28
+ * answers the stored `ImageStudioImage`. `400` for a malformed `documentId` or a missing file, `413` for a file over the
29
+ * image limit, `415` for a file that is no image, `501` without an `images` port.
30
+ */
31
+ async function imageUploadPost(ctx, request) {
32
+ const user = await authenticated(ctx, request);
33
+ const images = port(ctx, "images");
34
+ const form = await request.readForm?.().catch(() => null);
35
+ const documentId = form?.get("documentId");
36
+ if (typeof documentId !== "string" || !ASSET_DOCUMENT_ID.test(documentId)) throw new HttpError(400, "Ungültige Dokument-ID");
37
+ const file = form?.get("file");
38
+ if (!(file instanceof File)) throw new HttpError(400, "Keine Datei übermittelt");
39
+ if (mediaKindOf(file.type) !== "image") throw new HttpError(415, "Nur Bilddateien werden akzeptiert");
40
+ if (file.size > MAX_MEDIA_BYTES.image) throw new HttpError(413, `Datei ist größer als ${MAX_MEDIA_BYTES.image / 1024 / 1024} MB`);
41
+ return json(await images.upload(documentId, file, user));
42
+ }
43
+ //#endregion
44
+ export { imageGenerateBodySchema, imageGeneratePost, imagePromptBlocksGet, imageUploadPost };
45
+
46
+ //# sourceMappingURL=images.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"images.js","names":[],"sources":["../../../src/server/handlers/images.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { ImageStudioGenerateBody } from '../../core/backend-types'\nimport { ASSET_DOCUMENT_ID, MAX_MEDIA_BYTES, mediaKindOf } from '../../features/media/schema'\nimport { HttpError } from '../errors'\nimport { authenticated, type HandlerContext, type HandlerRequest, type HandlerResponse, json, port, readBody } from './context'\n\n// The Bild-Studio's three routes over the `images` port: the host's Prompt-Bausteine, one generation, one reference upload.\n// The host's provider owns the prompt, the model and the storage; these handlers only authenticate, validate and pass through.\n\n/** The Prompt-Bausteine the host manages (`ImageStudioPromptBlock[]`). `501` without an `images` port. */\nexport async function imagePromptBlocksGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await authenticated(ctx, request)\n return json(await port(ctx, 'images').promptBlocks(user))\n}\n\n/** Body schema of `imageGeneratePost`. */\nexport const imageGenerateBodySchema = z.object({\n description: z.string().max(10_000),\n instruction: z.string().max(4_000),\n promptBlockIds: z.array(z.string().min(1).max(200)).max(50),\n referenceUrls: z.array(z.string().min(1).max(2_048)).max(20),\n}) satisfies z.ZodType<ImageStudioGenerateBody>\n\n/** Generates one image for the body (`ImageStudioGenerateBody`); answers the stored `ImageStudioImage`. `400` for a malformed body, `501` without an `images` port. */\nexport async function imageGeneratePost(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await authenticated(ctx, request)\n const images = port(ctx, 'images')\n const body = await readBody(request, imageGenerateBodySchema)\n return json(await images.generate(body, user))\n}\n\n/**\n * Stores a reference image: multipart fields `documentId` (the document it belongs to, or `scratch`) and `file` (an image);\n * answers the stored `ImageStudioImage`. `400` for a malformed `documentId` or a missing file, `413` for a file over the\n * image limit, `415` for a file that is no image, `501` without an `images` port.\n */\nexport async function imageUploadPost(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await authenticated(ctx, request)\n const images = port(ctx, 'images')\n // A body that is no multipart form at all counts as „no file“.\n const form = await request.readForm?.().catch(() => null)\n const documentId = form?.get('documentId')\n if (typeof documentId !== 'string' || !ASSET_DOCUMENT_ID.test(documentId)) throw new HttpError(400, 'Ungültige Dokument-ID')\n const file = form?.get('file')\n if (!(file instanceof File)) throw new HttpError(400, 'Keine Datei übermittelt')\n if (mediaKindOf(file.type) !== 'image') throw new HttpError(415, 'Nur Bilddateien werden akzeptiert')\n if (file.size > MAX_MEDIA_BYTES.image) throw new HttpError(413, `Datei ist größer als ${MAX_MEDIA_BYTES.image / 1024 / 1024} MB`)\n return json(await images.upload(documentId, file, user))\n}\n"],"mappings":";;;;;;;AAUA,eAAsB,qBAAqB,KAAqB,SAAmD;CACjH,MAAM,OAAO,MAAM,cAAc,KAAK,OAAO;CAC7C,OAAO,KAAK,MAAM,KAAK,KAAK,QAAQ,CAAC,CAAC,aAAa,IAAI,CAAC;AAC1D;;AAGA,MAAa,0BAA0B,EAAE,OAAO;CAC9C,aAAa,EAAE,OAAO,CAAC,CAAC,IAAI,GAAM;CAClC,aAAa,EAAE,OAAO,CAAC,CAAC,IAAI,GAAK;CACjC,gBAAgB,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE;CAC1D,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAK,CAAC,CAAC,CAAC,IAAI,EAAE;AAC7D,CAAC;;AAGD,eAAsB,kBAAkB,KAAqB,SAAmD;CAC9G,MAAM,OAAO,MAAM,cAAc,KAAK,OAAO;CAC7C,MAAM,SAAS,KAAK,KAAK,QAAQ;CACjC,MAAM,OAAO,MAAM,SAAS,SAAS,uBAAuB;CAC5D,OAAO,KAAK,MAAM,OAAO,SAAS,MAAM,IAAI,CAAC;AAC/C;;;;;;AAOA,eAAsB,gBAAgB,KAAqB,SAAmD;CAC5G,MAAM,OAAO,MAAM,cAAc,KAAK,OAAO;CAC7C,MAAM,SAAS,KAAK,KAAK,QAAQ;CAEjC,MAAM,OAAO,MAAM,QAAQ,WAAW,CAAC,CAAC,YAAY,IAAI;CACxD,MAAM,aAAa,MAAM,IAAI,YAAY;CACzC,IAAI,OAAO,eAAe,YAAY,CAAC,kBAAkB,KAAK,UAAU,GAAG,MAAM,IAAI,UAAU,KAAK,uBAAuB;CAC3H,MAAM,OAAO,MAAM,IAAI,MAAM;CAC7B,IAAI,EAAE,gBAAgB,OAAO,MAAM,IAAI,UAAU,KAAK,yBAAyB;CAC/E,IAAI,YAAY,KAAK,IAAI,MAAM,SAAS,MAAM,IAAI,UAAU,KAAK,mCAAmC;CACpG,IAAI,KAAK,OAAO,gBAAgB,OAAO,MAAM,IAAI,UAAU,KAAK,wBAAwB,gBAAgB,QAAQ,OAAO,KAAK,IAAI;CAChI,OAAO,KAAK,MAAM,OAAO,OAAO,YAAY,MAAM,IAAI,CAAC;AACzD"}
@@ -3,4 +3,5 @@ import { completeBodySchema } from "../../features/ai/protocol/complete.js";
3
3
  import { AiHandlerContext, HandlerContext, HandlerRequest, HandlerResponse, authenticated, empty, enforceRateLimit, eventStream, json, port, readBody, text } from "./context.js";
4
4
  import { chatBodySchema, chatPost, completePost, extractBodySchema, extractPost, generatePost, modelsGet, transcriptCleanupBodySchema, transcriptCleanupPost } from "./ai.js";
5
5
  import { assetGet, assetPost, capabilitiesGet, listWorkspaceTags, settingsGet, settingsPut, settingsPutBodySchema, tagsGet, themeDelete, themeGet, themePost, themesGet, usersGet } from "./editor.js";
6
- export { type AiHandlerContext, type HandlerContext, type HandlerRequest, type HandlerResponse, assetGet, assetPost, authenticated, capabilitiesGet, chatBodySchema, chatPost, completeBodySchema, completePost, empty, enforceRateLimit, eventStream, extractBodySchema, extractPost, generateBodySchema, generatePost, json, listWorkspaceTags, modelsGet, port, readBody, settingsGet, settingsPut, settingsPutBodySchema, tagsGet, text, themeDelete, themeGet, themePost, themesGet, transcriptCleanupBodySchema, transcriptCleanupPost, usersGet };
6
+ import { imageGenerateBodySchema, imageGeneratePost, imagePromptBlocksGet, imageUploadPost } from "./images.js";
7
+ export { type AiHandlerContext, type HandlerContext, type HandlerRequest, type HandlerResponse, assetGet, assetPost, authenticated, capabilitiesGet, chatBodySchema, chatPost, completeBodySchema, completePost, empty, enforceRateLimit, eventStream, extractBodySchema, extractPost, generateBodySchema, generatePost, imageGenerateBodySchema, imageGeneratePost, imagePromptBlocksGet, imageUploadPost, json, listWorkspaceTags, modelsGet, port, readBody, settingsGet, settingsPut, settingsPutBodySchema, tagsGet, text, themeDelete, themeGet, themePost, themesGet, transcriptCleanupBodySchema, transcriptCleanupPost, usersGet };
@@ -3,4 +3,5 @@ import { generateBodySchema } from "../../features/ai/protocol/generate.js";
3
3
  import { authenticated, empty, enforceRateLimit, eventStream, json, port, readBody, text } from "./context.js";
4
4
  import { chatBodySchema, chatPost, completePost, extractBodySchema, extractPost, generatePost, modelsGet, transcriptCleanupBodySchema, transcriptCleanupPost } from "./ai.js";
5
5
  import { assetGet, assetPost, capabilitiesGet, listWorkspaceTags, settingsGet, settingsPut, settingsPutBodySchema, tagsGet, themeDelete, themeGet, themePost, themesGet, usersGet } from "./editor.js";
6
- export { assetGet, assetPost, authenticated, capabilitiesGet, chatBodySchema, chatPost, completeBodySchema, completePost, empty, enforceRateLimit, eventStream, extractBodySchema, extractPost, generateBodySchema, generatePost, json, listWorkspaceTags, modelsGet, port, readBody, settingsGet, settingsPut, settingsPutBodySchema, tagsGet, text, themeDelete, themeGet, themePost, themesGet, transcriptCleanupBodySchema, transcriptCleanupPost, usersGet };
6
+ import { imageGenerateBodySchema, imageGeneratePost, imagePromptBlocksGet, imageUploadPost } from "./images.js";
7
+ export { assetGet, assetPost, authenticated, capabilitiesGet, chatBodySchema, chatPost, completeBodySchema, completePost, empty, enforceRateLimit, eventStream, extractBodySchema, extractPost, generateBodySchema, generatePost, imageGenerateBodySchema, imageGeneratePost, imagePromptBlocksGet, imageUploadPost, json, listWorkspaceTags, modelsGet, port, readBody, settingsGet, settingsPut, settingsPutBodySchema, tagsGet, text, themeDelete, themeGet, themePost, themesGet, transcriptCleanupBodySchema, transcriptCleanupPost, usersGet };
@@ -3,9 +3,9 @@ import { AuthenticateRequest } from "./auth.js";
3
3
  import { ChatMessage } from "../features/ai/protocol/generate.js";
4
4
  import { HttpError } from "./errors.js";
5
5
  import { AiModels, AiModelsConfig, ChatTurnPart, createAiModels, streamChat, streamChatTurn, upstreamError } from "./ai.js";
6
- import { AssetStore, CollabStateStore, Directory, DocumentAdmin, DocumentStore, EditorPorts, Identity, ModelProvider, PORT_CAPABILITY, SettingsStore, ThemeStore, serverCapabilities } from "./ports.js";
6
+ import { AssetStore, CollabStateStore, Directory, DocumentAdmin, DocumentStore, EditorPorts, Identity, ImageProvider, ModelProvider, PORT_CAPABILITY, SettingsStore, ThemeStore, serverCapabilities } from "./ports.js";
7
7
  import { COLLAB_FIELD, CollabConnection, CollabServer, CollabServerOptions, WebSocketLike, createCollabServer } from "./collab.js";
8
8
  import { decodeCollabState, encodeCollabState, isStateStale } from "./collab-state.js";
9
9
  import { RateLimiter, RateLimiterOptions, createRateLimiter, userKey } from "./rate-limit.js";
10
10
  import { closeEditorServer, collabContentChanged, collabStats, enforceAiRateLimit, forgetCollabDocument, listAiModels, liveContent, resolveModel, setEditorPorts, useAiModels, useCollabServer, useEditorPorts } from "./host.js";
11
- export { type AiModels, type AiModelsConfig, type AssetStore, type AuthenticateRequest, COLLAB_FIELD, type ChatMessage, type ChatTurnPart, type CollabConnection, type CollabServer, type CollabServerOptions, type CollabStateStore, type Directory, type DocumentAdmin, type DocumentStore, type EditorPorts, HttpError, type Identity, type ModelProvider, PORT_CAPABILITY, type PresetConfig, type RateLimiter, type RateLimiterOptions, type SettingsStore, type ThemeStore, type TranscriptionConfig, type WebSocketLike, closeEditorServer, collabContentChanged, collabStats, createAiModels, createCollabServer, createRateLimiter, decodeCollabState, encodeCollabState, enforceAiRateLimit, forgetCollabDocument, isStateStale, listAiModels, liveContent, resolveModel, serverCapabilities, setEditorPorts, streamChat, streamChatTurn, upstreamError, useAiModels, useCollabServer, useEditorPorts, userKey };
11
+ export { type AiModels, type AiModelsConfig, type AssetStore, type AuthenticateRequest, COLLAB_FIELD, type ChatMessage, type ChatTurnPart, type CollabConnection, type CollabServer, type CollabServerOptions, type CollabStateStore, type Directory, type DocumentAdmin, type DocumentStore, type EditorPorts, HttpError, type Identity, type ImageProvider, type ModelProvider, PORT_CAPABILITY, type PresetConfig, type RateLimiter, type RateLimiterOptions, type SettingsStore, type ThemeStore, type TranscriptionConfig, type WebSocketLike, closeEditorServer, collabContentChanged, collabStats, createAiModels, createCollabServer, createRateLimiter, decodeCollabState, encodeCollabState, enforceAiRateLimit, forgetCollabDocument, isStateStale, listAiModels, liveContent, resolveModel, serverCapabilities, setEditorPorts, streamChat, streamChatTurn, upstreamError, useAiModels, useCollabServer, useEditorPorts, userKey };
@@ -2,7 +2,7 @@ import { AssetRecord } from "../features/media/assets.js";
2
2
  import "../features/media/schema.js";
3
3
  import { EditorSettings } from "../settings/index.js";
4
4
  import { BackendCapability } from "../core/backend-capability.js";
5
- import { MentionUser, ModelProfile, ModelRef, ThemeRecord } from "../core/backend-types.js";
5
+ import { ImageStudioGenerateBody, ImageStudioImage, ImageStudioPromptBlock, MentionUser, ModelProfile, ModelRef, ThemeRecord } from "../core/backend-types.js";
6
6
  import "../features/mentions/schema.js";
7
7
  import { AuthenticateRequest } from "./auth.js";
8
8
  import "../features/ai/protocol/index.js";
@@ -122,6 +122,15 @@ export interface ModelProvider {
122
122
  */
123
123
  listModels?(user: object | null): Promise<ModelProfile[] | null>;
124
124
  }
125
+ /** The host's image generation for the Bild-Studio: its managed Prompt-Bausteine, the generator and the storage of uploaded references. Without this port the image routes answer 501. */
126
+ export interface ImageProvider {
127
+ /** The Prompt-Bausteine the host manages, in the order it lists them (`GET /api/editor/images/prompt-blocks`). `user` is what `authenticate` returned. */
128
+ promptBlocks(user: object): Promise<ImageStudioPromptBlock[]>;
129
+ /** Generates one image and stores it (`POST /api/editor/images/generate`); resolves with the stored image (`active: false`). The host builds the prompt from `body`. */
130
+ generate(body: ImageStudioGenerateBody, user: object): Promise<ImageStudioImage>;
131
+ /** Stores an uploaded reference image of document `documentId` (`POST /api/editor/images/upload`); resolves with the stored image (`active: true`). */
132
+ upload(documentId: string, file: File, user: object): Promise<ImageStudioImage>;
133
+ }
125
134
  /** Everything the editor's server reads from the host, one port per capability. Only `identity` and `documents` are required. */
126
135
  export interface EditorPorts {
127
136
  identity: Identity;
@@ -133,6 +142,7 @@ export interface EditorPorts {
133
142
  settings?: SettingsStore;
134
143
  themes?: ThemeStore;
135
144
  models?: ModelProvider;
145
+ images?: ImageProvider;
136
146
  /** Where the comment threads live (`adapters/comments/{json,drizzle}`). */
137
147
  comments?: CommentStore;
138
148
  }
@@ -8,7 +8,8 @@ const PORT_CAPABILITY = {
8
8
  settings: "settings",
9
9
  themes: "themes",
10
10
  models: "ai",
11
- comments: "comments"
11
+ comments: "comments",
12
+ images: "images"
12
13
  };
13
14
  /**
14
15
  * The capabilities `ports` serves: those of every present port, plus `tags` (derived from `documents.listContents`).
@@ -1 +1 @@
1
- {"version":3,"file":"ports.js","names":[],"sources":["../../src/server/ports.ts"],"sourcesContent":["import type { JSONContent } from '@tiptap/core'\nimport type { LanguageModel } from 'ai'\nimport type { CommentStore } from '../adapters/comments/types'\nimport type { BackendCapability } from '../core/backend-capability'\nimport type { ModelProfile, ModelRef } from '../features/ai/protocol'\nimport type { AssetRecord } from '../features/media/schema'\nimport type { MentionUser } from '../features/mentions/schema'\nimport type { ThemeRecord } from '../features/themes/schema'\nimport type { EditorSettings } from '../settings'\nimport type { AuthenticateRequest } from './auth'\n\n// The server side of the editor's backend, split by capability: a host (or an adapter) implements the ports it serves\n// and leaves the others out; routes and features that need an absent port answer 501.\n\n/** Who a request or websocket connection belongs to, and which documents that user may open. */\nexport interface Identity {\n /**\n * The user behind a request, `null` = not authenticated (HTTP routes answer 401). Collaboration: called in Hocuspocus\n * `onAuthenticate` with the upgrade request's `headers` and the client's `token` (`undefined` when it sent none, so cookie\n * hosts keep working and bearer-only hosts read `token`); `null` closes the connection with 4401. With the Nuxt module\n * option `collab.authenticateUpgrade` it runs once at the upgrade with the `headers` only and `null` refuses the upgrade\n * with HTTP 401. The value becomes the connection context's `user`.\n */\n authenticate(request: AuthenticateRequest): Promise<object | null>\n /**\n * Per-document access for the collaboration websocket, called in Hocuspocus `onAuthenticate` (right after `authenticate`) with the user.\n * `null` rejects the connection; `{ readOnly: true }` lets it sync but applies none of its edits. Omit it to let every\n * authenticated user edit every document.\n */\n authorize?(user: object, id: string): Promise<{ readOnly: boolean } | null>\n}\n\n/** The stored TipTap JSON of the workspace's documents. */\nexport interface DocumentStore {\n /** TipTap JSON to seed a document's Y.Doc from; `null` = the document does not exist. */\n loadContent(id: string): Promise<JSONContent | null>\n /** Mirror of the current Yjs state, called debounced after edits and on shutdown. Must tolerate ids deleted meanwhile. */\n storeContent(id: string, content: JSONContent): Promise<void>\n /**\n * Opaque version of the stored content of a document (mtime, hash, revision), `null` = unknown, no NUL. The collab server\n * keeps it next to the Yjs state. When it differs from the live version (the content changed behind the editor's back) the\n * state is discarded on load, removed through `removeState` and the document is re-seeded from `loadContent`. A document\n * that is open when its version moves is not written back: the next store merges the new `loadContent` into the open\n * Y.Doc. The version is read right after every `storeContent`, so it must reflect what the editor itself just wrote.\n * Omit it to never discard state.\n */\n contentVersion?(id: string): Promise<string | null>\n /** Stored TipTap JSON of every document in the workspace, e.g. to collect the `#` tags in use (`GET /api/editor/tags`). */\n listContents(): Promise<{ id: string, content: JSONContent }[]>\n}\n\n/** Document management a host needs next to the editor's own storage methods: `DocumentStore.storeContent` only updates. */\nexport interface DocumentAdmin {\n /** Creates the document or replaces its content; the content version changes. */\n putDocument(id: string, content: JSONContent): Promise<void>\n /** Removes the content, the Yjs state and the assets of a document; unknown ids are fine. Call `forgetCollabDocument(id)` first when it may be open. */\n deleteDocument(id: string): Promise<void>\n /** Ids of all stored documents. */\n listDocumentIds(): Promise<string[]>\n}\n\n/** Where the collaboration server keeps the Yjs state of open documents. */\nexport interface CollabStateStore {\n /** The bytes `storeState` stored (opaque to the host: the Yjs update behind a content-version prefix; state stored by earlier releases is a raw update and still loads); `null` = none yet (the Y.Doc is then seeded from `loadContent`). */\n loadState(id: string): Promise<Uint8Array | null>\n /** Persists the state bytes unchanged; called on every store and when block IDs were backfilled. Must tolerate ids deleted meanwhile. */\n storeState(id: string, state: Uint8Array): Promise<void>\n /** Drops the Yjs state of a deleted document (the next load re-seeds from `loadContent`). */\n removeState(id: string): Promise<void>\n}\n\n/** The users that can be `@`-mentioned. */\nexport interface Directory {\n /** All users that can be `@`-mentioned (served by `GET /api/editor/users`); must not include credentials. */\n listUsers(): Promise<MentionUser[]>\n}\n\n/** Uploaded files. */\nexport interface AssetStore {\n /** Stores an uploaded file (`GET /api/editor/assets/:id/:name` serves it). `id` is a document id or `SCRATCH_DOCUMENT_ID`; it may name no existing document. */\n storeAsset(id: string, asset: AssetRecord & { data: Buffer }): Promise<void>\n /** A stored file with its record; `null` = unknown id or name. */\n loadAsset(id: string, name: string): Promise<{ record: AssetRecord, data: Buffer } | null>\n}\n\n/** Per-user editor settings. Without this port the client keeps settings in localStorage only. */\nexport interface SettingsStore {\n /** Per-user editor settings as stored (`null` = none yet), validated by `GET /api/editor/settings`. */\n loadSettings(user: object): Promise<unknown | null>\n /** Stores validated settings for the user from `authenticate`. */\n storeSettings(user: object, settings: EditorSettings): Promise<void>\n}\n\n/** The theme library shared by all users. Without this port the themes feature is uninstalled: its routes answer 501. */\nexport interface ThemeStore {\n /** All themes (`GET /api/editor/themes`). */\n listThemes(): Promise<ThemeRecord[]>\n /** A theme's record and `theme.css` (`GET /api/editor/themes/:id`); `null` = unknown id. */\n loadTheme(id: string): Promise<{ record: ThemeRecord, css: string } | null>\n /** Stores an uploaded theme (`POST /api/editor/themes`). */\n storeTheme(record: Omit<ThemeRecord, 'builtin'>, css: string): Promise<void>\n /** Deletes an uploaded theme (`DELETE /api/editor/themes/:id`); `false` = not found or builtin. */\n removeTheme(id: string): Promise<boolean>\n}\n\n/** The host's own language models for the AI endpoints; both members fall through to the runtime-config presets. */\nexport interface ModelProvider {\n /**\n * The language model for `ref`, so the AI endpoints use the host's own providers; consulted before the runtime-config\n * presets for every model reference. `user` is what `authenticate` returned. `null` falls through to the presets\n * (and answers 400 for a preset that is not configured either).\n */\n languageModel?(ref: ModelRef, user: object | null): Promise<LanguageModel | null>\n /**\n * The models `GET /api/ai/models` lists for `user`, instead of the runtime-config presets (list them with\n * `source: 'preset'` and the `id` that `languageModel` receives as `{ preset: id }`). `null` (or omitting the method)\n * lists the presets.\n */\n listModels?(user: object | null): Promise<ModelProfile[] | null>\n}\n\n/** Everything the editor's server reads from the host, one port per capability. Only `identity` and `documents` are required. */\nexport interface EditorPorts {\n identity: Identity\n documents: DocumentStore & Partial<DocumentAdmin>\n /** Required by the collaboration server. */\n collabState?: CollabStateStore\n directory?: Directory\n assets?: AssetStore\n settings?: SettingsStore\n themes?: ThemeStore\n models?: ModelProvider\n /** Where the comment threads live (`adapters/comments/{json,drizzle}`). */\n comments?: CommentStore\n}\n\n/** The capability each optional port provides; `identity` is no capability, `tags` derive from `documents` (see `serverCapabilities`). */\nexport const PORT_CAPABILITY: Record<Exclude<keyof EditorPorts, 'identity'>, BackendCapability> = {\n documents: 'documents',\n collabState: 'collaboration',\n directory: 'users',\n assets: 'assets',\n settings: 'settings',\n themes: 'themes',\n models: 'ai',\n comments: 'comments',\n}\n\n/**\n * The capabilities `ports` serves: those of every present port, plus `tags` (derived from `documents.listContents`).\n * Two capabilities depend on more than a port: `ai` (the AI routes need configured models) and `collaboration` (the\n * websocket needs the collab server). `served` states what the installed server really mounts and overrides the port-based\n * answer for them; omitted keys follow the ports.\n */\nexport function serverCapabilities(ports: EditorPorts, served: { ai?: boolean, collaboration?: boolean } = {}): BackendCapability[] {\n const capabilities = new Set<BackendCapability>(\n (Object.keys(PORT_CAPABILITY) as (keyof typeof PORT_CAPABILITY)[])\n .filter(port => ports[port] !== undefined)\n .map(port => PORT_CAPABILITY[port]),\n )\n for (const capability of ['ai', 'collaboration'] as const) {\n if (served[capability] === true) capabilities.add(capability)\n else if (served[capability] === false) capabilities.delete(capability)\n }\n if (ports.documents) capabilities.add('tags')\n return [...capabilities]\n}\n"],"mappings":";;AAyIA,MAAa,kBAAqF;CAChG,WAAW;CACX,aAAa;CACb,WAAW;CACX,QAAQ;CACR,UAAU;CACV,QAAQ;CACR,QAAQ;CACR,UAAU;AACZ;;;;;;;AAQA,SAAgB,mBAAmB,OAAoB,SAAoD,CAAC,GAAwB;CAClI,MAAM,eAAe,IAAI,IACtB,OAAO,KAAK,eAAe,CAAC,CAC1B,QAAO,SAAQ,MAAM,UAAU,KAAA,CAAS,CAAC,CACzC,KAAI,SAAQ,gBAAgB,KAAK,CACtC;CACA,KAAK,MAAM,cAAc,CAAC,MAAM,eAAe,GAC7C,IAAI,OAAO,gBAAgB,MAAM,aAAa,IAAI,UAAU;MACvD,IAAI,OAAO,gBAAgB,OAAO,aAAa,OAAO,UAAU;CAEvE,IAAI,MAAM,WAAW,aAAa,IAAI,MAAM;CAC5C,OAAO,CAAC,GAAG,YAAY;AACzB"}
1
+ {"version":3,"file":"ports.js","names":[],"sources":["../../src/server/ports.ts"],"sourcesContent":["import type { JSONContent } from '@tiptap/core'\nimport type { LanguageModel } from 'ai'\nimport type { CommentStore } from '../adapters/comments/types'\nimport type { BackendCapability } from '../core/backend-capability'\nimport type { ImageStudioGenerateBody, ImageStudioImage, ImageStudioPromptBlock } from '../core/backend-types'\nimport type { ModelProfile, ModelRef } from '../features/ai/protocol'\nimport type { AssetRecord } from '../features/media/schema'\nimport type { MentionUser } from '../features/mentions/schema'\nimport type { ThemeRecord } from '../features/themes/schema'\nimport type { EditorSettings } from '../settings'\nimport type { AuthenticateRequest } from './auth'\n\n// The server side of the editor's backend, split by capability: a host (or an adapter) implements the ports it serves\n// and leaves the others out; routes and features that need an absent port answer 501.\n\n/** Who a request or websocket connection belongs to, and which documents that user may open. */\nexport interface Identity {\n /**\n * The user behind a request, `null` = not authenticated (HTTP routes answer 401). Collaboration: called in Hocuspocus\n * `onAuthenticate` with the upgrade request's `headers` and the client's `token` (`undefined` when it sent none, so cookie\n * hosts keep working and bearer-only hosts read `token`); `null` closes the connection with 4401. With the Nuxt module\n * option `collab.authenticateUpgrade` it runs once at the upgrade with the `headers` only and `null` refuses the upgrade\n * with HTTP 401. The value becomes the connection context's `user`.\n */\n authenticate(request: AuthenticateRequest): Promise<object | null>\n /**\n * Per-document access for the collaboration websocket, called in Hocuspocus `onAuthenticate` (right after `authenticate`) with the user.\n * `null` rejects the connection; `{ readOnly: true }` lets it sync but applies none of its edits. Omit it to let every\n * authenticated user edit every document.\n */\n authorize?(user: object, id: string): Promise<{ readOnly: boolean } | null>\n}\n\n/** The stored TipTap JSON of the workspace's documents. */\nexport interface DocumentStore {\n /** TipTap JSON to seed a document's Y.Doc from; `null` = the document does not exist. */\n loadContent(id: string): Promise<JSONContent | null>\n /** Mirror of the current Yjs state, called debounced after edits and on shutdown. Must tolerate ids deleted meanwhile. */\n storeContent(id: string, content: JSONContent): Promise<void>\n /**\n * Opaque version of the stored content of a document (mtime, hash, revision), `null` = unknown, no NUL. The collab server\n * keeps it next to the Yjs state. When it differs from the live version (the content changed behind the editor's back) the\n * state is discarded on load, removed through `removeState` and the document is re-seeded from `loadContent`. A document\n * that is open when its version moves is not written back: the next store merges the new `loadContent` into the open\n * Y.Doc. The version is read right after every `storeContent`, so it must reflect what the editor itself just wrote.\n * Omit it to never discard state.\n */\n contentVersion?(id: string): Promise<string | null>\n /** Stored TipTap JSON of every document in the workspace, e.g. to collect the `#` tags in use (`GET /api/editor/tags`). */\n listContents(): Promise<{ id: string, content: JSONContent }[]>\n}\n\n/** Document management a host needs next to the editor's own storage methods: `DocumentStore.storeContent` only updates. */\nexport interface DocumentAdmin {\n /** Creates the document or replaces its content; the content version changes. */\n putDocument(id: string, content: JSONContent): Promise<void>\n /** Removes the content, the Yjs state and the assets of a document; unknown ids are fine. Call `forgetCollabDocument(id)` first when it may be open. */\n deleteDocument(id: string): Promise<void>\n /** Ids of all stored documents. */\n listDocumentIds(): Promise<string[]>\n}\n\n/** Where the collaboration server keeps the Yjs state of open documents. */\nexport interface CollabStateStore {\n /** The bytes `storeState` stored (opaque to the host: the Yjs update behind a content-version prefix; state stored by earlier releases is a raw update and still loads); `null` = none yet (the Y.Doc is then seeded from `loadContent`). */\n loadState(id: string): Promise<Uint8Array | null>\n /** Persists the state bytes unchanged; called on every store and when block IDs were backfilled. Must tolerate ids deleted meanwhile. */\n storeState(id: string, state: Uint8Array): Promise<void>\n /** Drops the Yjs state of a deleted document (the next load re-seeds from `loadContent`). */\n removeState(id: string): Promise<void>\n}\n\n/** The users that can be `@`-mentioned. */\nexport interface Directory {\n /** All users that can be `@`-mentioned (served by `GET /api/editor/users`); must not include credentials. */\n listUsers(): Promise<MentionUser[]>\n}\n\n/** Uploaded files. */\nexport interface AssetStore {\n /** Stores an uploaded file (`GET /api/editor/assets/:id/:name` serves it). `id` is a document id or `SCRATCH_DOCUMENT_ID`; it may name no existing document. */\n storeAsset(id: string, asset: AssetRecord & { data: Buffer }): Promise<void>\n /** A stored file with its record; `null` = unknown id or name. */\n loadAsset(id: string, name: string): Promise<{ record: AssetRecord, data: Buffer } | null>\n}\n\n/** Per-user editor settings. Without this port the client keeps settings in localStorage only. */\nexport interface SettingsStore {\n /** Per-user editor settings as stored (`null` = none yet), validated by `GET /api/editor/settings`. */\n loadSettings(user: object): Promise<unknown | null>\n /** Stores validated settings for the user from `authenticate`. */\n storeSettings(user: object, settings: EditorSettings): Promise<void>\n}\n\n/** The theme library shared by all users. Without this port the themes feature is uninstalled: its routes answer 501. */\nexport interface ThemeStore {\n /** All themes (`GET /api/editor/themes`). */\n listThemes(): Promise<ThemeRecord[]>\n /** A theme's record and `theme.css` (`GET /api/editor/themes/:id`); `null` = unknown id. */\n loadTheme(id: string): Promise<{ record: ThemeRecord, css: string } | null>\n /** Stores an uploaded theme (`POST /api/editor/themes`). */\n storeTheme(record: Omit<ThemeRecord, 'builtin'>, css: string): Promise<void>\n /** Deletes an uploaded theme (`DELETE /api/editor/themes/:id`); `false` = not found or builtin. */\n removeTheme(id: string): Promise<boolean>\n}\n\n/** The host's own language models for the AI endpoints; both members fall through to the runtime-config presets. */\nexport interface ModelProvider {\n /**\n * The language model for `ref`, so the AI endpoints use the host's own providers; consulted before the runtime-config\n * presets for every model reference. `user` is what `authenticate` returned. `null` falls through to the presets\n * (and answers 400 for a preset that is not configured either).\n */\n languageModel?(ref: ModelRef, user: object | null): Promise<LanguageModel | null>\n /**\n * The models `GET /api/ai/models` lists for `user`, instead of the runtime-config presets (list them with\n * `source: 'preset'` and the `id` that `languageModel` receives as `{ preset: id }`). `null` (or omitting the method)\n * lists the presets.\n */\n listModels?(user: object | null): Promise<ModelProfile[] | null>\n}\n\n/** The host's image generation for the Bild-Studio: its managed Prompt-Bausteine, the generator and the storage of uploaded references. Without this port the image routes answer 501. */\nexport interface ImageProvider {\n /** The Prompt-Bausteine the host manages, in the order it lists them (`GET /api/editor/images/prompt-blocks`). `user` is what `authenticate` returned. */\n promptBlocks(user: object): Promise<ImageStudioPromptBlock[]>\n /** Generates one image and stores it (`POST /api/editor/images/generate`); resolves with the stored image (`active: false`). The host builds the prompt from `body`. */\n generate(body: ImageStudioGenerateBody, user: object): Promise<ImageStudioImage>\n /** Stores an uploaded reference image of document `documentId` (`POST /api/editor/images/upload`); resolves with the stored image (`active: true`). */\n upload(documentId: string, file: File, user: object): Promise<ImageStudioImage>\n}\n\n/** Everything the editor's server reads from the host, one port per capability. Only `identity` and `documents` are required. */\nexport interface EditorPorts {\n identity: Identity\n documents: DocumentStore & Partial<DocumentAdmin>\n /** Required by the collaboration server. */\n collabState?: CollabStateStore\n directory?: Directory\n assets?: AssetStore\n settings?: SettingsStore\n themes?: ThemeStore\n models?: ModelProvider\n images?: ImageProvider\n /** Where the comment threads live (`adapters/comments/{json,drizzle}`). */\n comments?: CommentStore\n}\n\n/** The capability each optional port provides; `identity` is no capability, `tags` derive from `documents` (see `serverCapabilities`). */\nexport const PORT_CAPABILITY: Record<Exclude<keyof EditorPorts, 'identity'>, BackendCapability> = {\n documents: 'documents',\n collabState: 'collaboration',\n directory: 'users',\n assets: 'assets',\n settings: 'settings',\n themes: 'themes',\n models: 'ai',\n comments: 'comments',\n images: 'images',\n}\n\n/**\n * The capabilities `ports` serves: those of every present port, plus `tags` (derived from `documents.listContents`).\n * Two capabilities depend on more than a port: `ai` (the AI routes need configured models) and `collaboration` (the\n * websocket needs the collab server). `served` states what the installed server really mounts and overrides the port-based\n * answer for them; omitted keys follow the ports.\n */\nexport function serverCapabilities(ports: EditorPorts, served: { ai?: boolean, collaboration?: boolean } = {}): BackendCapability[] {\n const capabilities = new Set<BackendCapability>(\n (Object.keys(PORT_CAPABILITY) as (keyof typeof PORT_CAPABILITY)[])\n .filter(port => ports[port] !== undefined)\n .map(port => PORT_CAPABILITY[port]),\n )\n for (const capability of ['ai', 'collaboration'] as const) {\n if (served[capability] === true) capabilities.add(capability)\n else if (served[capability] === false) capabilities.delete(capability)\n }\n if (ports.documents) capabilities.add('tags')\n return [...capabilities]\n}\n"],"mappings":";;AAqJA,MAAa,kBAAqF;CAChG,WAAW;CACX,aAAa;CACb,WAAW;CACX,QAAQ;CACR,UAAU;CACV,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,QAAQ;AACV;;;;;;;AAQA,SAAgB,mBAAmB,OAAoB,SAAoD,CAAC,GAAwB;CAClI,MAAM,eAAe,IAAI,IACtB,OAAO,KAAK,eAAe,CAAC,CAC1B,QAAO,SAAQ,MAAM,UAAU,KAAA,CAAS,CAAC,CACzC,KAAI,SAAQ,gBAAgB,KAAK,CACtC;CACA,KAAK,MAAM,cAAc,CAAC,MAAM,eAAe,GAC7C,IAAI,OAAO,gBAAgB,MAAM,aAAa,IAAI,UAAU;MACvD,IAAI,OAAO,gBAAgB,OAAO,aAAa,OAAO,UAAU;CAEvE,IAAI,MAAM,WAAW,aAAa,IAAI,MAAM;CAC5C,OAAO,CAAC,GAAG,YAAY;AACzB"}
@@ -1,6 +1,7 @@
1
1
  import { AI_API, CAPABILITIES_ROUTE, COMMENTS_ROUTE, DOCUMENT_ROUTE, EDITOR_API } from "../core/api-routes.js";
2
2
  import { chatPost, completePost, extractPost, generatePost, modelsGet, transcriptCleanupPost } from "./handlers/ai.js";
3
3
  import { assetGet, assetPost, capabilitiesGet, settingsGet, settingsPut, tagsGet, themeDelete, themeGet, themePost, themesGet, usersGet } from "./handlers/editor.js";
4
+ import { imageGeneratePost, imagePromptBlocksGet, imageUploadPost } from "./handlers/images.js";
4
5
  import { commentsGet, commentsPost, documentGet, documentPut } from "../adapters/documents.js";
5
6
  //#region src/server/routes.ts
6
7
  const ROUTES = [
@@ -46,6 +47,24 @@ const ROUTES = [
46
47
  port: "assets",
47
48
  handler: assetGet
48
49
  },
50
+ {
51
+ method: "get",
52
+ path: `${EDITOR_API}/images/prompt-blocks`,
53
+ port: "images",
54
+ handler: imagePromptBlocksGet
55
+ },
56
+ {
57
+ method: "post",
58
+ path: `${EDITOR_API}/images/generate`,
59
+ port: "images",
60
+ handler: imageGeneratePost
61
+ },
62
+ {
63
+ method: "post",
64
+ path: `${EDITOR_API}/images/upload`,
65
+ port: "images",
66
+ handler: imageUploadPost
67
+ },
49
68
  {
50
69
  method: "get",
51
70
  path: `${EDITOR_API}/themes`,
@@ -1 +1 @@
1
- {"version":3,"file":"routes.js","names":[],"sources":["../../src/server/routes.ts"],"sourcesContent":["import { commentsGet, commentsPost, documentGet, documentPut } from '../adapters/documents'\nimport { AI_API, CAPABILITIES_ROUTE, COMMENTS_ROUTE, DOCUMENT_ROUTE, EDITOR_API } from '../core/api-routes'\nimport type { RouteHandler } from './h3'\nimport {\n type AiHandlerContext,\n assetGet,\n assetPost,\n capabilitiesGet,\n chatPost,\n completePost,\n extractPost,\n generatePost,\n type HandlerContext,\n modelsGet,\n settingsGet,\n settingsPut,\n tagsGet,\n themeDelete,\n themeGet,\n themePost,\n themesGet,\n transcriptCleanupPost,\n usersGet,\n} from './handlers'\nimport type { EditorPorts } from './ports'\n\n// The one HTTP route table of the editor backend: what each route needs (a port, or the AI models), and its handler.\n// `serverRoutesOf` keeps the routes the host's ports can serve; `installEditorServer` (`./server/nitro`) mounts them on\n// a Nitro app, the Nuxt module through its server plugin. Paths are the constants of `core/api-routes.ts`.\n\ntype Method = 'get' | 'put' | 'post' | 'delete'\n/** A port other than `identity`, which every route needs and none is gated on. */\ntype RoutePort = Exclude<keyof EditorPorts, 'identity'>\n\nexport type RouteDef =\n | { method: Method, path: string, port: RoutePort, handler: RouteHandler<HandlerContext> }\n | { method: Method, path: string, port: 'ai', handler: RouteHandler<AiHandlerContext> }\n\nconst ROUTES: readonly RouteDef[] = [\n // What the server serves: `documents` is required, so this route is always there.\n { method: 'get', path: CAPABILITIES_ROUTE, port: 'documents', handler: capabilitiesGet },\n { method: 'get', path: `${EDITOR_API}/settings`, port: 'settings', handler: settingsGet },\n { method: 'put', path: `${EDITOR_API}/settings`, port: 'settings', handler: settingsPut },\n { method: 'get', path: `${EDITOR_API}/users`, port: 'directory', handler: usersGet },\n // Tags derive from the contents the documents port lists.\n { method: 'get', path: `${EDITOR_API}/tags`, port: 'documents', handler: tagsGet },\n { method: 'post', path: `${EDITOR_API}/assets/:id`, port: 'assets', handler: assetPost },\n { method: 'get', path: `${EDITOR_API}/assets/:id/:name`, port: 'assets', handler: assetGet },\n { method: 'get', path: `${EDITOR_API}/themes`, port: 'themes', handler: themesGet },\n { method: 'post', path: `${EDITOR_API}/themes`, port: 'themes', handler: themePost },\n { method: 'get', path: `${EDITOR_API}/themes/:id`, port: 'themes', handler: themeGet },\n { method: 'delete', path: `${EDITOR_API}/themes/:id`, port: 'themes', handler: themeDelete },\n { method: 'get', path: DOCUMENT_ROUTE, port: 'documents', handler: documentGet },\n { method: 'put', path: DOCUMENT_ROUTE, port: 'documents', handler: documentPut },\n { method: 'get', path: COMMENTS_ROUTE, port: 'comments', handler: commentsGet },\n { method: 'post', path: COMMENTS_ROUTE, port: 'comments', handler: commentsPost },\n { method: 'get', path: `${AI_API}/models`, port: 'ai', handler: modelsGet },\n { method: 'post', path: `${AI_API}/chat`, port: 'ai', handler: chatPost },\n { method: 'post', path: `${AI_API}/complete`, port: 'ai', handler: completePost },\n { method: 'post', path: `${AI_API}/generate`, port: 'ai', handler: generatePost },\n { method: 'post', path: `${AI_API}/extract`, port: 'ai', handler: extractPost },\n { method: 'post', path: `${AI_API}/transcript-cleanup`, port: 'ai', handler: transcriptCleanupPost },\n]\n\n/**\n * The routes `ports` can serve. A route is served iff its port is present (`documents` is required, so its routes always\n * are); the AI routes (`port: 'ai'`) iff `options.ai`, the host having configured the AI models.\n */\nexport function serverRoutesOf(ports: EditorPorts, options: { ai: boolean }): RouteDef[] {\n return ROUTES.filter(route => route.port === 'ai' ? options.ai : ports[route.port] !== undefined)\n}\n"],"mappings":";;;;;AAsCA,MAAM,SAA8B;CAElC;EAAE,QAAQ;EAAO,MAAM;EAAoB,MAAM;EAAa,SAAS;CAAgB;CACvF;EAAE,QAAQ;EAAO,MAAM,GAAG,WAAW;EAAY,MAAM;EAAY,SAAS;CAAY;CACxF;EAAE,QAAQ;EAAO,MAAM,GAAG,WAAW;EAAY,MAAM;EAAY,SAAS;CAAY;CACxF;EAAE,QAAQ;EAAO,MAAM,GAAG,WAAW;EAAS,MAAM;EAAa,SAAS;CAAS;CAEnF;EAAE,QAAQ;EAAO,MAAM,GAAG,WAAW;EAAQ,MAAM;EAAa,SAAS;CAAQ;CACjF;EAAE,QAAQ;EAAQ,MAAM,GAAG,WAAW;EAAc,MAAM;EAAU,SAAS;CAAU;CACvF;EAAE,QAAQ;EAAO,MAAM,GAAG,WAAW;EAAoB,MAAM;EAAU,SAAS;CAAS;CAC3F;EAAE,QAAQ;EAAO,MAAM,GAAG,WAAW;EAAU,MAAM;EAAU,SAAS;CAAU;CAClF;EAAE,QAAQ;EAAQ,MAAM,GAAG,WAAW;EAAU,MAAM;EAAU,SAAS;CAAU;CACnF;EAAE,QAAQ;EAAO,MAAM,GAAG,WAAW;EAAc,MAAM;EAAU,SAAS;CAAS;CACrF;EAAE,QAAQ;EAAU,MAAM,GAAG,WAAW;EAAc,MAAM;EAAU,SAAS;CAAY;CAC3F;EAAE,QAAQ;EAAO,MAAM;EAAgB,MAAM;EAAa,SAAS;CAAY;CAC/E;EAAE,QAAQ;EAAO,MAAM;EAAgB,MAAM;EAAa,SAAS;CAAY;CAC/E;EAAE,QAAQ;EAAO,MAAM;EAAgB,MAAM;EAAY,SAAS;CAAY;CAC9E;EAAE,QAAQ;EAAQ,MAAM;EAAgB,MAAM;EAAY,SAAS;CAAa;CAChF;EAAE,QAAQ;EAAO,MAAM,GAAG,OAAO;EAAU,MAAM;EAAM,SAAS;CAAU;CAC1E;EAAE,QAAQ;EAAQ,MAAM,GAAG,OAAO;EAAQ,MAAM;EAAM,SAAS;CAAS;CACxE;EAAE,QAAQ;EAAQ,MAAM,GAAG,OAAO;EAAY,MAAM;EAAM,SAAS;CAAa;CAChF;EAAE,QAAQ;EAAQ,MAAM,GAAG,OAAO;EAAY,MAAM;EAAM,SAAS;CAAa;CAChF;EAAE,QAAQ;EAAQ,MAAM,GAAG,OAAO;EAAW,MAAM;EAAM,SAAS;CAAY;CAC9E;EAAE,QAAQ;EAAQ,MAAM,GAAG,OAAO;EAAsB,MAAM;EAAM,SAAS;CAAsB;AACrG;;;;;AAMA,SAAgB,eAAe,OAAoB,SAAsC;CACvF,OAAO,OAAO,QAAO,UAAS,MAAM,SAAS,OAAO,QAAQ,KAAK,MAAM,MAAM,UAAU,KAAA,CAAS;AAClG"}
1
+ {"version":3,"file":"routes.js","names":[],"sources":["../../src/server/routes.ts"],"sourcesContent":["import { commentsGet, commentsPost, documentGet, documentPut } from '../adapters/documents'\nimport { AI_API, CAPABILITIES_ROUTE, COMMENTS_ROUTE, DOCUMENT_ROUTE, EDITOR_API } from '../core/api-routes'\nimport type { RouteHandler } from './h3'\nimport {\n type AiHandlerContext,\n assetGet,\n assetPost,\n capabilitiesGet,\n chatPost,\n completePost,\n extractPost,\n generatePost,\n type HandlerContext,\n imageGeneratePost,\n imagePromptBlocksGet,\n imageUploadPost,\n modelsGet,\n settingsGet,\n settingsPut,\n tagsGet,\n themeDelete,\n themeGet,\n themePost,\n themesGet,\n transcriptCleanupPost,\n usersGet,\n} from './handlers'\nimport type { EditorPorts } from './ports'\n\n// The one HTTP route table of the editor backend: what each route needs (a port, or the AI models), and its handler.\n// `serverRoutesOf` keeps the routes the host's ports can serve; `installEditorServer` (`./server/nitro`) mounts them on\n// a Nitro app, the Nuxt module through its server plugin. Paths are the constants of `core/api-routes.ts`.\n\ntype Method = 'get' | 'put' | 'post' | 'delete'\n/** A port other than `identity`, which every route needs and none is gated on. */\ntype RoutePort = Exclude<keyof EditorPorts, 'identity'>\n\nexport type RouteDef =\n | { method: Method, path: string, port: RoutePort, handler: RouteHandler<HandlerContext> }\n | { method: Method, path: string, port: 'ai', handler: RouteHandler<AiHandlerContext> }\n\nconst ROUTES: readonly RouteDef[] = [\n // What the server serves: `documents` is required, so this route is always there.\n { method: 'get', path: CAPABILITIES_ROUTE, port: 'documents', handler: capabilitiesGet },\n { method: 'get', path: `${EDITOR_API}/settings`, port: 'settings', handler: settingsGet },\n { method: 'put', path: `${EDITOR_API}/settings`, port: 'settings', handler: settingsPut },\n { method: 'get', path: `${EDITOR_API}/users`, port: 'directory', handler: usersGet },\n // Tags derive from the contents the documents port lists.\n { method: 'get', path: `${EDITOR_API}/tags`, port: 'documents', handler: tagsGet },\n { method: 'post', path: `${EDITOR_API}/assets/:id`, port: 'assets', handler: assetPost },\n { method: 'get', path: `${EDITOR_API}/assets/:id/:name`, port: 'assets', handler: assetGet },\n { method: 'get', path: `${EDITOR_API}/images/prompt-blocks`, port: 'images', handler: imagePromptBlocksGet },\n { method: 'post', path: `${EDITOR_API}/images/generate`, port: 'images', handler: imageGeneratePost },\n { method: 'post', path: `${EDITOR_API}/images/upload`, port: 'images', handler: imageUploadPost },\n { method: 'get', path: `${EDITOR_API}/themes`, port: 'themes', handler: themesGet },\n { method: 'post', path: `${EDITOR_API}/themes`, port: 'themes', handler: themePost },\n { method: 'get', path: `${EDITOR_API}/themes/:id`, port: 'themes', handler: themeGet },\n { method: 'delete', path: `${EDITOR_API}/themes/:id`, port: 'themes', handler: themeDelete },\n { method: 'get', path: DOCUMENT_ROUTE, port: 'documents', handler: documentGet },\n { method: 'put', path: DOCUMENT_ROUTE, port: 'documents', handler: documentPut },\n { method: 'get', path: COMMENTS_ROUTE, port: 'comments', handler: commentsGet },\n { method: 'post', path: COMMENTS_ROUTE, port: 'comments', handler: commentsPost },\n { method: 'get', path: `${AI_API}/models`, port: 'ai', handler: modelsGet },\n { method: 'post', path: `${AI_API}/chat`, port: 'ai', handler: chatPost },\n { method: 'post', path: `${AI_API}/complete`, port: 'ai', handler: completePost },\n { method: 'post', path: `${AI_API}/generate`, port: 'ai', handler: generatePost },\n { method: 'post', path: `${AI_API}/extract`, port: 'ai', handler: extractPost },\n { method: 'post', path: `${AI_API}/transcript-cleanup`, port: 'ai', handler: transcriptCleanupPost },\n]\n\n/**\n * The routes `ports` can serve. A route is served iff its port is present (`documents` is required, so its routes always\n * are); the AI routes (`port: 'ai'`) iff `options.ai`, the host having configured the AI models.\n */\nexport function serverRoutesOf(ports: EditorPorts, options: { ai: boolean }): RouteDef[] {\n return ROUTES.filter(route => route.port === 'ai' ? options.ai : ports[route.port] !== undefined)\n}\n"],"mappings":";;;;;;AAyCA,MAAM,SAA8B;CAElC;EAAE,QAAQ;EAAO,MAAM;EAAoB,MAAM;EAAa,SAAS;CAAgB;CACvF;EAAE,QAAQ;EAAO,MAAM,GAAG,WAAW;EAAY,MAAM;EAAY,SAAS;CAAY;CACxF;EAAE,QAAQ;EAAO,MAAM,GAAG,WAAW;EAAY,MAAM;EAAY,SAAS;CAAY;CACxF;EAAE,QAAQ;EAAO,MAAM,GAAG,WAAW;EAAS,MAAM;EAAa,SAAS;CAAS;CAEnF;EAAE,QAAQ;EAAO,MAAM,GAAG,WAAW;EAAQ,MAAM;EAAa,SAAS;CAAQ;CACjF;EAAE,QAAQ;EAAQ,MAAM,GAAG,WAAW;EAAc,MAAM;EAAU,SAAS;CAAU;CACvF;EAAE,QAAQ;EAAO,MAAM,GAAG,WAAW;EAAoB,MAAM;EAAU,SAAS;CAAS;CAC3F;EAAE,QAAQ;EAAO,MAAM,GAAG,WAAW;EAAwB,MAAM;EAAU,SAAS;CAAqB;CAC3G;EAAE,QAAQ;EAAQ,MAAM,GAAG,WAAW;EAAmB,MAAM;EAAU,SAAS;CAAkB;CACpG;EAAE,QAAQ;EAAQ,MAAM,GAAG,WAAW;EAAiB,MAAM;EAAU,SAAS;CAAgB;CAChG;EAAE,QAAQ;EAAO,MAAM,GAAG,WAAW;EAAU,MAAM;EAAU,SAAS;CAAU;CAClF;EAAE,QAAQ;EAAQ,MAAM,GAAG,WAAW;EAAU,MAAM;EAAU,SAAS;CAAU;CACnF;EAAE,QAAQ;EAAO,MAAM,GAAG,WAAW;EAAc,MAAM;EAAU,SAAS;CAAS;CACrF;EAAE,QAAQ;EAAU,MAAM,GAAG,WAAW;EAAc,MAAM;EAAU,SAAS;CAAY;CAC3F;EAAE,QAAQ;EAAO,MAAM;EAAgB,MAAM;EAAa,SAAS;CAAY;CAC/E;EAAE,QAAQ;EAAO,MAAM;EAAgB,MAAM;EAAa,SAAS;CAAY;CAC/E;EAAE,QAAQ;EAAO,MAAM;EAAgB,MAAM;EAAY,SAAS;CAAY;CAC9E;EAAE,QAAQ;EAAQ,MAAM;EAAgB,MAAM;EAAY,SAAS;CAAa;CAChF;EAAE,QAAQ;EAAO,MAAM,GAAG,OAAO;EAAU,MAAM;EAAM,SAAS;CAAU;CAC1E;EAAE,QAAQ;EAAQ,MAAM,GAAG,OAAO;EAAQ,MAAM;EAAM,SAAS;CAAS;CACxE;EAAE,QAAQ;EAAQ,MAAM,GAAG,OAAO;EAAY,MAAM;EAAM,SAAS;CAAa;CAChF;EAAE,QAAQ;EAAQ,MAAM,GAAG,OAAO;EAAY,MAAM;EAAM,SAAS;CAAa;CAChF;EAAE,QAAQ;EAAQ,MAAM,GAAG,OAAO;EAAW,MAAM;EAAM,SAAS;CAAY;CAC9E;EAAE,QAAQ;EAAQ,MAAM,GAAG,OAAO;EAAsB,MAAM;EAAM,SAAS;CAAsB;AACrG;;;;;AAMA,SAAgB,eAAe,OAAoB,SAAsC;CACvF,OAAO,OAAO,QAAO,UAAS,MAAM,SAAS,OAAO,QAAQ,KAAK,MAAM,MAAM,UAAU,KAAA,CAAS;AAClG"}
@@ -60,7 +60,7 @@ const _hoisted_18 = {
60
60
  class: "flex min-w-0 flex-1 flex-col gap-0.5"
61
61
  };
62
62
  const _hoisted_19 = ["title", "aria-label"];
63
- const _hoisted_20 = ["title"];
63
+ const _hoisted_20 = ["title", "data-label-from-body"];
64
64
  const _hoisted_21 = { class: "rounded-sm bg-[color-mix(in_oklab,var(--sk-primary)_20%,transparent)] text-inherit" };
65
65
  const _hoisted_22 = { class: "flex opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 has-data-[state=open]:opacity-100" };
66
66
  const _hoisted_23 = {
@@ -440,13 +440,14 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
440
440
  "data-basket-dot": ""
441
441
  }, null, 12, _hoisted_19)) : createCommentVNode("v-if", true),
442
442
  createElementVNode("span", {
443
- class: "min-w-0 flex-1 truncate",
444
- title: item.label
443
+ class: normalizeClass(["min-w-0 flex-1 truncate", item.labelFromBody && "italic text-[var(--sk-muted)]"]),
444
+ title: item.label,
445
+ "data-label-from-body": item.labelFromBody || void 0
445
446
  }, [labelParts(item) ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
446
447
  createTextVNode(toDisplayString(labelParts(item)[0]), 1),
447
448
  createElementVNode("mark", _hoisted_21, toDisplayString(labelParts(item)[1]), 1),
448
449
  createTextVNode(toDisplayString(labelParts(item)[2]), 1)
449
- ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(item.label), 1)], 64))], 8, _hoisted_20),
450
+ ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(item.label), 1)], 64))], 10, _hoisted_20),
450
451
  item.hasSummary ? (openBlock(), createBlock(unref(UIcon), {
451
452
  key: 1,
452
453
  name: "i-lucide-text-quote",
@@ -1 +1 @@
1
- {"version":3,"file":"DocumentTree.js","names":["_useModel","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_unref","_withKeys","_createSlots","_withCtx","_createCommentVNode","_createBlock","_Fragment","_renderList","_normalizeClass","_normalizeStyle","_toDisplayString","_withModifiers","_createTextVNode","_renderSlot"],"sources":["../../src/tree/DocumentTree.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { Editor } from '@tiptap/core'\nimport { computed } from 'vue'\nimport { BLOCK_MIME, blockTransferOf, readBlockTransfer, writeBlockTransfer } from '../core/block-transfer'\nimport { findBlockById } from '../core/schema/block-ids'\nimport type { DropdownItem } from '../vue/kit/types'\nimport { useUiKit } from '../vue/kit/use-ui-kit'\nimport { useEditorSettings } from '../vue/settings'\nimport { useEditorI18n } from '../vue/use-editor-i18n'\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 React twin renders the same model.\n\nconst props = defineProps<{\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}>()\n\ndefineSlots<{\n /** Host actions on a document row, rendered next to its delete button (e.g. a download link). */\n 'document-actions'?: (props: { id: string, title: string }) => unknown\n}>()\n\n/** The folder new documents, folders and host imports go to. Reset to `''` when it is deleted or moved away. */\nconst selectedFolder = defineModel<string>('selectedFolder', { default: '' })\n\nconst { t } = useEditorI18n()\nconst { Button: UButton, DropdownMenu: UDropdownMenu, Icon: UIcon, Input: UInput, Popover: UPopover, Tooltip: UTooltip, Tree: UTree } = useUiKit()\nconst { model, state } = useTreeModel({ store: () => props.store, editor: () => props.editor, selectedFolder })\nconst { settings } = useEditorSettings()\n\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.\nconst canUseFileRoot = computed(() => state.value.hasEditor && state.value.structure === 'composite')\n\nfunction toggleTreeRoot() {\n settings.value.blocks.treeRoot = settings.value.blocks.treeRoot === 'file' ? 'folders' : 'file'\n}\n\nconst row = (item: TreeItem) => rowOf(state.value, item, t)\n\n/** The basket `item` is in, when the open editor offers it (the row shows its colour as a dot). */\nconst basketOf = (item: TreeItem) => itemBasket(item, state.value.baskets)\n\n/** The basket filter button needs a live editor that offers baskets. */\nconst showBasketFilter = computed(() => state.value.hasEditor && state.value.baskets.length > 0)\n/** The chosen baskets the open editor offers (an unknown id never filters, see `TreeModel`). */\nconst shownBaskets = computed(() => state.value.basketFilter.filter(id => basketById(state.value.baskets, id)))\nconst basketOption = 'flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm text-[var(--sk-fg)] transition-colors duration-150 hover:bg-[var(--sk-bg-elevated)] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--sk-primary)]'\n\n/** The breadcrumb of the focus: the document (first crumb, leaves focus) and the Bausteine down to the focused one. */\nconst breadcrumb = computed(() => focusCrumbs(state.value.focusPath))\n\n/** Why dropping the drag on `item` is refused, while the pointer is over it. */\nfunction refusal(item: TreeItem): string | null {\n const drop = row(item).drop\n return drop && !drop.ok ? drop.reason : null\n}\n\n/** UTree reports the keys of the rows it wants open; the model decides what that changes. */\nfunction onExpanded(next: string[]) {\n const wanted = new Set(next)\n const open = new Set(state.value.view.open)\n for (const key of wanted) if (!open.has(key)) model.value.setOpen(key, true)\n for (const key of open) if (!wanted.has(key)) model.value.setOpen(key, false)\n}\n\n/** The label split around the part the title filter matched, `null` when it did not match here. */\nfunction labelParts(item: TreeItem): [string, string, string] | null {\n const match = row(item).match\n if (!match) return null\n const [at, length] = match\n return [item.label.slice(0, at), item.label.slice(at, at + length), item.label.slice(at + length)]\n}\n\n// Folders and the root toggle through the tree's own row click; documents and outline entries (whose row click opens or\n// reveals) toggle through their chevron.\nfunction onChevronClick(event: Event, item: TreeItem) {\n if (item.type === 'root' || item.type === 'folder') return\n event.stopPropagation()\n model.value.toggle(item.key)\n}\n\nfunction filterMenu(item: TreeItem): DropdownItem[][] {\n const { filterKinds, hiddenKinds } = row(item)\n const groups: DropdownItem[][] = [[\n { type: 'label', label: t('tree.filter.show') },\n ...filterKinds.map<DropdownItem>(kind => ({\n label: kind.label,\n icon: kind.icon,\n type: 'checkbox',\n checked: !hiddenKinds.includes(kind.id),\n onUpdateChecked: visible => model.value.setKindHidden(item.key, kind.id, !visible),\n // Stay open: several kinds are usually toggled in one go.\n onSelect: event => event.preventDefault(),\n })),\n ]]\n if (hiddenKinds.length) groups.push([{ label: t('tree.filter.reset'), icon: 'i-lucide-filter-x', onSelect: () => model.value.clearFilter(item.key) }])\n return groups\n}\n\nfunction addMenu(item: TreeItem): DropdownItem[][] {\n return model.value.addMenu(item).map(group => group.map(entry => ({ label: entry.label, icon: entry.icon, onSelect: entry.run })))\n}\n\n// --- drag & drop -----------------------------------------------------------------------------------------------\n\nfunction onDragStart(event: DragEvent, item: TreeItem) {\n const payload = model.value.dragStart(item)\n if (!payload) return event.preventDefault()\n if (!event.dataTransfer) return model.value.dragEnd()\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 editor = props.editor\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). */\nconst ownDrag = () => !!state.value.drag && state.value.drag.source.type !== 'foreign-block'\n\nfunction onDragOver(event: DragEvent, item: TreeItem) {\n const foreign = !ownDrag() && !!event.dataTransfer?.types.includes(BLOCK_MIME)\n const verdict = foreign ? model.value.foreignDragOver(item) : model.value.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 if (event.dataTransfer) event.dataTransfer.dropEffect = verdict.ok ? (foreign ? 'copy' : 'move') : 'none'\n}\n\nfunction onDragLeave(event: DragEvent, item: TreeItem) {\n if (!(event.currentTarget as Node).contains(event.relatedTarget as Node | null)) model.value.dragLeave(item)\n}\n\nfunction onDrop(event: DragEvent, item: TreeItem) {\n if (ownDrag()) {\n event.preventDefault()\n return model.value.drop(item)\n }\n const payload = event.dataTransfer ? readBlockTransfer(event.dataTransfer) : null\n if (!payload) return\n event.preventDefault()\n return model.value.dropForeign(item, payload, event.shiftKey, event.clientX, event.clientY)\n}\n</script>\n\n<template>\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex items-center gap-1\">\n <UInput\n :model-value=\"state.filterText\"\n class=\"min-w-0 flex-1\"\n icon=\"i-lucide-search\"\n size=\"sm\"\n :placeholder=\"t('tree.filterText.placeholder')\"\n :aria-label=\"t('tree.filterText.placeholder')\"\n @update:model-value=\"model.setFilterText($event)\"\n @keydown.esc=\"model.setFilterText('')\"\n >\n <template v-if=\"state.filterText\" #trailing>\n <UButton\n icon=\"i-lucide-x\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"link\"\n :aria-label=\"t('tree.filterText.clear')\"\n @click=\"model.setFilterText('')\"\n />\n </template>\n </UInput>\n <!-- A popover, not a dropdown menu: a menu item cannot show a dot in an arbitrary CSS colour. -->\n <UPopover\n v-if=\"showBasketFilter\"\n :content=\"{ side: 'bottom', align: 'end', sideOffset: 4, onCloseAutoFocus: (event: Event) => event.preventDefault() }\"\n :ui=\"{ content: 'w-44 p-1' }\"\n >\n <UButton\n icon=\"i-lucide-paint-bucket\"\n size=\"xs\"\n :color=\"shownBaskets.length ? 'primary' : 'neutral'\"\n :variant=\"shownBaskets.length ? 'soft' : 'ghost'\"\n :title=\"t('tree.basketFilter.label')\"\n :aria-label=\"t('tree.basketFilter.label')\"\n />\n <template #content>\n <div role=\"menu\" :aria-label=\"t('tree.basketFilter.label')\">\n <button\n v-for=\"basket in state.baskets\"\n :key=\"basket.id\"\n type=\"button\"\n role=\"menuitemcheckbox\"\n :aria-checked=\"shownBaskets.includes(basket.id)\"\n :class=\"basketOption\"\n @click=\"model.setBasketShown(basket.id, !shownBaskets.includes(basket.id))\"\n >\n <span class=\"size-2.5 shrink-0 rounded-full\" :style=\"{ backgroundColor: basket.color }\" />\n <span class=\"truncate\">{{ basket.label }}</span>\n <UIcon v-if=\"shownBaskets.includes(basket.id)\" name=\"i-lucide-check\" class=\"ml-auto size-3.5 shrink-0\" />\n </button>\n <button v-if=\"shownBaskets.length\" type=\"button\" role=\"menuitem\" :class=\"basketOption\" @click=\"model.clearBasketFilter()\">\n <UIcon name=\"i-lucide-filter-x\" class=\"size-3.5 shrink-0 text-[var(--sk-muted)]\" />\n <span class=\"truncate\">{{ t('tree.basketFilter.reset') }}</span>\n </button>\n </div>\n </template>\n </UPopover>\n </div>\n\n <div class=\"flex items-center justify-end\">\n <UTooltip :text=\"canUseFileRoot ? '' : t('tree.root.unavailable')\">\n <!-- A disabled button swallows pointer events, so the tooltip hangs on the wrapper. -->\n <span class=\"inline-flex\">\n <UButton\n :icon=\"state.virtual ? 'i-lucide-file-stack' : 'i-lucide-folder-tree'\"\n :label=\"t(state.virtual ? 'tree.root.file' : 'tree.root.folders')\"\n size=\"xs\"\n color=\"neutral\"\n :variant=\"state.virtual ? 'soft' : 'ghost'\"\n :disabled=\"!canUseFileRoot\"\n :aria-pressed=\"state.virtual\"\n @click=\"toggleTreeRoot\"\n />\n </span>\n </UTooltip>\n </div>\n\n <div v-if=\"state.view.hasKindFilters\" class=\"flex items-center\">\n <UButton\n icon=\"i-lucide-filter-x\"\n :label=\"t('tree.filter.reset')\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"subtle\"\n @click=\"model.clearAllFilters\"\n />\n </div>\n\n <div v-if=\"state.creatingFolder\" class=\"flex flex-col gap-1\">\n <UInput\n :model-value=\"state.creatingFolder.draft\"\n size=\"sm\"\n autofocus\n :color=\"state.creatingFolder.error ? 'error' : undefined\"\n :highlight=\"!!state.creatingFolder.error\"\n :placeholder=\"state.creatingFolder.parent ? t('tree.folder.placeholderIn', { parent: state.creatingFolder.parent }) : t('tree.folder.placeholder')\"\n :aria-label=\"t('tree.folder.name')\"\n :aria-invalid=\"!!state.creatingFolder.error\"\n @update:model-value=\"model.setFolderDraft($event)\"\n @keydown.enter=\"model.commitCreateFolder\"\n @keydown.esc=\"model.cancelCreateFolder\"\n @blur=\"model.cancelCreateFolder\"\n />\n <p v-if=\"state.creatingFolder.error\" class=\"px-0.5 text-xs text-[var(--sk-danger)]\" role=\"alert\">\n {{ state.creatingFolder.error }}\n </p>\n </div>\n\n <div v-if=\"breadcrumb.crumbs.length\" class=\"flex flex-wrap items-center gap-0.5 text-xs\">\n <UButton\n icon=\"i-lucide-corner-up-left\"\n :label=\"breadcrumb.document?.label ?? t('tree.focus.root')\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"ghost\"\n :title=\"t('tree.focus.leave')\"\n :aria-label=\"t('tree.focus.leave')\"\n @click=\"model.defocus()\"\n />\n <template v-for=\"(crumb, index) in breadcrumb.crumbs\" :key=\"crumb.key\">\n <UIcon name=\"i-lucide-chevron-right\" class=\"size-3 shrink-0 text-[var(--sk-muted)]\" />\n <span v-if=\"index === breadcrumb.crumbs.length - 1\" class=\"min-w-0 truncate px-1.5 font-medium\" aria-current=\"location\">{{ crumb.label }}</span>\n <UButton v-else :label=\"crumb.label\" size=\"xs\" color=\"neutral\" variant=\"ghost\" @click=\"model.focus(crumb.key)\" />\n </template>\n </div>\n\n <p v-if=\"!state.tree && !state.virtual\" class=\"px-2.5 py-1.5 text-sm text-[var(--sk-muted)]\">\n {{ state.tree === null ? t('tree.loadFailed') : t('tree.loading') }}\n </p>\n\n <UTree\n v-else\n :expanded=\"state.view.open\"\n :items=\"state.view.items\"\n :get-key=\"(item: TreeItem) => item.key\"\n size=\"sm\"\n :aria-label=\"t('tree.aria')\"\n @update:expanded=\"onExpanded\"\n >\n <template #item-wrapper=\"{ item }: { item: TreeItem }\">\n <div class=\"w-full\">\n <div\n class=\"group flex w-full cursor-pointer items-center gap-1.5 rounded-md px-2 py-1 text-sm select-none hover:bg-[color-mix(in_oklab,var(--sk-bg-elevated)_60%,transparent)]\"\n :class=\"[\n row(item).active && 'bg-[color-mix(in_oklab,var(--sk-primary)_10%,transparent)] font-medium text-[var(--sk-primary)]',\n row(item).selected && 'bg-[var(--sk-bg-elevated)]',\n row(item).drop?.ok && 'ring-1 ring-[var(--sk-primary)]',\n refusal(item) && 'bg-[color-mix(in_oklab,var(--sk-danger)_10%,transparent)] ring-1 ring-[var(--sk-danger)]',\n row(item).dimmed && 'opacity-60',\n ]\"\n :draggable=\"model.isDraggable(item)\"\n @dragstart=\"onDragStart($event, item)\"\n @dragover=\"onDragOver($event, item)\"\n @dragleave=\"onDragLeave($event, item)\"\n @drop=\"onDrop($event, item)\"\n @dragend=\"model.dragEnd\"\n >\n <UIcon\n v-if=\"row(item).expandable\"\n name=\"i-lucide-chevron-right\"\n class=\"size-3.5 shrink-0 text-[var(--sk-muted)] transition-transform\"\n :class=\"row(item).open && 'rotate-90'\"\n @click=\"onChevronClick($event, item)\"\n />\n <span v-else-if=\"item.type === 'composite' || item.type === 'heading'\" class=\"size-3.5 shrink-0\" />\n <UIcon :name=\"item.icon\" class=\"size-4 shrink-0\" :style=\"item.tone ? { color: item.tone } : undefined\" />\n\n <div v-if=\"row(item).renaming\" class=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <UInput\n :model-value=\"row(item).renaming!.draft\"\n size=\"xs\"\n class=\"min-w-0\"\n autofocus\n :color=\"row(item).renaming!.error ? 'error' : undefined\"\n :highlight=\"!!row(item).renaming!.error\"\n :aria-label=\"t('tree.folder.name')\"\n :aria-invalid=\"!!row(item).renaming!.error\"\n @update:model-value=\"model.setRenameDraft($event)\"\n @focus=\"($event.target as HTMLInputElement).select()\"\n @click.stop\n @keydown.stop\n @keydown.enter=\"model.commitRename\"\n @keydown.esc=\"model.cancelRename\"\n @blur=\"model.blurRename\"\n />\n <p v-if=\"row(item).renaming!.error\" class=\"text-xs text-[var(--sk-danger)]\" role=\"alert\" @click.stop>\n {{ row(item).renaming!.error }}\n </p>\n </div>\n <template v-else>\n <span\n v-if=\"basketOf(item)\"\n class=\"size-2 shrink-0 rounded-full\"\n :style=\"{ backgroundColor: basketOf(item)!.color }\"\n :title=\"basketOf(item)!.label\"\n :aria-label=\"basketOf(item)!.label\"\n role=\"img\"\n data-basket-dot\n />\n <span class=\"min-w-0 flex-1 truncate\" :title=\"item.label\">\n <template v-if=\"labelParts(item)\">{{ labelParts(item)![0] }}<mark class=\"rounded-sm bg-[color-mix(in_oklab,var(--sk-primary)_20%,transparent)] text-inherit\">{{ labelParts(item)![1] }}</mark>{{ labelParts(item)![2] }}</template>\n <template v-else>{{ item.label }}</template>\n </span>\n <UIcon\n v-if=\"item.hasSummary\"\n name=\"i-lucide-text-quote\"\n class=\"size-3.5 shrink-0 text-[var(--sk-muted)]\"\n :title=\"t('tree.summary')\"\n />\n <div class=\"flex shrink-0\" @click.stop>\n <UDropdownMenu v-if=\"row(item).showFilter\" :items=\"filterMenu(item)\" :content=\"{ align: 'start' }\">\n <UButton\n icon=\"i-lucide-filter\"\n size=\"xs\"\n :color=\"row(item).hiddenKinds.length ? 'primary' : 'neutral'\"\n :variant=\"row(item).hiddenKinds.length ? 'soft' : 'ghost'\"\n :class=\"!row(item).hiddenKinds.length && 'opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 data-[state=open]:opacity-100'\"\n :title=\"t('tree.filter.title')\"\n :aria-label=\"t('tree.filter.title')\"\n />\n </UDropdownMenu>\n <div class=\"flex opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 has-data-[state=open]:opacity-100\">\n <UButton\n v-if=\"item.type === 'composite' && item.blockId\"\n icon=\"i-lucide-focus\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"ghost\"\n :title=\"t('tree.focus.enter')\"\n :aria-label=\"t('tree.focus.enter')\"\n @click=\"model.focus(item.key)\"\n />\n <UButton\n v-if=\"model.canMerge(item)\"\n icon=\"i-lucide-merge\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"ghost\"\n :title=\"t('tree.merge')\"\n :aria-label=\"t('tree.merge')\"\n @click=\"model.merge(item)\"\n />\n <UDropdownMenu v-if=\"addMenu(item).length\" :items=\"addMenu(item)\" :content=\"{ align: 'start', onCloseAutoFocus: (event: Event) => event.preventDefault() }\">\n <UButton\n icon=\"i-lucide-plus\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"ghost\"\n :title=\"t('tree.add')\"\n :aria-label=\"t('tree.add')\"\n />\n </UDropdownMenu>\n <slot v-if=\"item.type === 'document'\" name=\"document-actions\" :id=\"item.id!\" :title=\"item.label\" />\n <UButton\n v-if=\"item.type === 'document' || item.type === 'folder'\"\n icon=\"i-lucide-trash-2\"\n size=\"xs\"\n color=\"error\"\n variant=\"ghost\"\n :aria-label=\"t('tree.delete')\"\n loading-auto\n @click=\"model.remove(item)\"\n />\n </div>\n </div>\n </template>\n </div>\n <p v-if=\"refusal(item)\" class=\"px-2 pt-0.5 text-xs text-[var(--sk-danger)]\" role=\"status\">\n {{ refusal(item) }}\n </p>\n </div>\n </template>\n </UTree>\n\n <p v-if=\"state.view.noMatches\" class=\"px-2.5 py-1.5 text-sm text-[var(--sk-muted)]\">\n {{ t('tree.noMatches') }}\n </p>\n <p v-if=\"state.view.isEmpty\" class=\"px-2.5 py-1.5 text-sm text-[var(--sk-muted)]\">\n {{ t('tree.empty') }}\n </p>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"DocumentTree.js","names":["_useModel","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_unref","_withKeys","_createSlots","_withCtx","_createCommentVNode","_createBlock","_Fragment","_renderList","_normalizeClass","_normalizeStyle","_toDisplayString","_withModifiers","_createTextVNode","_renderSlot"],"sources":["../../src/tree/DocumentTree.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { Editor } from '@tiptap/core'\nimport { computed } from 'vue'\nimport { BLOCK_MIME, blockTransferOf, readBlockTransfer, writeBlockTransfer } from '../core/block-transfer'\nimport { findBlockById } from '../core/schema/block-ids'\nimport type { DropdownItem } from '../vue/kit/types'\nimport { useUiKit } from '../vue/kit/use-ui-kit'\nimport { useEditorSettings } from '../vue/settings'\nimport { useEditorI18n } from '../vue/use-editor-i18n'\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 React twin renders the same model.\n\nconst props = defineProps<{\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}>()\n\ndefineSlots<{\n /** Host actions on a document row, rendered next to its delete button (e.g. a download link). */\n 'document-actions'?: (props: { id: string, title: string }) => unknown\n}>()\n\n/** The folder new documents, folders and host imports go to. Reset to `''` when it is deleted or moved away. */\nconst selectedFolder = defineModel<string>('selectedFolder', { default: '' })\n\nconst { t } = useEditorI18n()\nconst { Button: UButton, DropdownMenu: UDropdownMenu, Icon: UIcon, Input: UInput, Popover: UPopover, Tooltip: UTooltip, Tree: UTree } = useUiKit()\nconst { model, state } = useTreeModel({ store: () => props.store, editor: () => props.editor, selectedFolder })\nconst { settings } = useEditorSettings()\n\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.\nconst canUseFileRoot = computed(() => state.value.hasEditor && state.value.structure === 'composite')\n\nfunction toggleTreeRoot() {\n settings.value.blocks.treeRoot = settings.value.blocks.treeRoot === 'file' ? 'folders' : 'file'\n}\n\nconst row = (item: TreeItem) => rowOf(state.value, item, t)\n\n/** The basket `item` is in, when the open editor offers it (the row shows its colour as a dot). */\nconst basketOf = (item: TreeItem) => itemBasket(item, state.value.baskets)\n\n/** The basket filter button needs a live editor that offers baskets. */\nconst showBasketFilter = computed(() => state.value.hasEditor && state.value.baskets.length > 0)\n/** The chosen baskets the open editor offers (an unknown id never filters, see `TreeModel`). */\nconst shownBaskets = computed(() => state.value.basketFilter.filter(id => basketById(state.value.baskets, id)))\nconst basketOption = 'flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm text-[var(--sk-fg)] transition-colors duration-150 hover:bg-[var(--sk-bg-elevated)] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--sk-primary)]'\n\n/** The breadcrumb of the focus: the document (first crumb, leaves focus) and the Bausteine down to the focused one. */\nconst breadcrumb = computed(() => focusCrumbs(state.value.focusPath))\n\n/** Why dropping the drag on `item` is refused, while the pointer is over it. */\nfunction refusal(item: TreeItem): string | null {\n const drop = row(item).drop\n return drop && !drop.ok ? drop.reason : null\n}\n\n/** UTree reports the keys of the rows it wants open; the model decides what that changes. */\nfunction onExpanded(next: string[]) {\n const wanted = new Set(next)\n const open = new Set(state.value.view.open)\n for (const key of wanted) if (!open.has(key)) model.value.setOpen(key, true)\n for (const key of open) if (!wanted.has(key)) model.value.setOpen(key, false)\n}\n\n/** The label split around the part the title filter matched, `null` when it did not match here. */\nfunction labelParts(item: TreeItem): [string, string, string] | null {\n const match = row(item).match\n if (!match) return null\n const [at, length] = match\n return [item.label.slice(0, at), item.label.slice(at, at + length), item.label.slice(at + length)]\n}\n\n// Folders and the root toggle through the tree's own row click; documents and outline entries (whose row click opens or\n// reveals) toggle through their chevron.\nfunction onChevronClick(event: Event, item: TreeItem) {\n if (item.type === 'root' || item.type === 'folder') return\n event.stopPropagation()\n model.value.toggle(item.key)\n}\n\nfunction filterMenu(item: TreeItem): DropdownItem[][] {\n const { filterKinds, hiddenKinds } = row(item)\n const groups: DropdownItem[][] = [[\n { type: 'label', label: t('tree.filter.show') },\n ...filterKinds.map<DropdownItem>(kind => ({\n label: kind.label,\n icon: kind.icon,\n type: 'checkbox',\n checked: !hiddenKinds.includes(kind.id),\n onUpdateChecked: visible => model.value.setKindHidden(item.key, kind.id, !visible),\n // Stay open: several kinds are usually toggled in one go.\n onSelect: event => event.preventDefault(),\n })),\n ]]\n if (hiddenKinds.length) groups.push([{ label: t('tree.filter.reset'), icon: 'i-lucide-filter-x', onSelect: () => model.value.clearFilter(item.key) }])\n return groups\n}\n\nfunction addMenu(item: TreeItem): DropdownItem[][] {\n return model.value.addMenu(item).map(group => group.map(entry => ({ label: entry.label, icon: entry.icon, onSelect: entry.run })))\n}\n\n// --- drag & drop -----------------------------------------------------------------------------------------------\n\nfunction onDragStart(event: DragEvent, item: TreeItem) {\n const payload = model.value.dragStart(item)\n if (!payload) return event.preventDefault()\n if (!event.dataTransfer) return model.value.dragEnd()\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 editor = props.editor\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). */\nconst ownDrag = () => !!state.value.drag && state.value.drag.source.type !== 'foreign-block'\n\nfunction onDragOver(event: DragEvent, item: TreeItem) {\n const foreign = !ownDrag() && !!event.dataTransfer?.types.includes(BLOCK_MIME)\n const verdict = foreign ? model.value.foreignDragOver(item) : model.value.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 if (event.dataTransfer) event.dataTransfer.dropEffect = verdict.ok ? (foreign ? 'copy' : 'move') : 'none'\n}\n\nfunction onDragLeave(event: DragEvent, item: TreeItem) {\n if (!(event.currentTarget as Node).contains(event.relatedTarget as Node | null)) model.value.dragLeave(item)\n}\n\nfunction onDrop(event: DragEvent, item: TreeItem) {\n if (ownDrag()) {\n event.preventDefault()\n return model.value.drop(item)\n }\n const payload = event.dataTransfer ? readBlockTransfer(event.dataTransfer) : null\n if (!payload) return\n event.preventDefault()\n return model.value.dropForeign(item, payload, event.shiftKey, event.clientX, event.clientY)\n}\n</script>\n\n<template>\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex items-center gap-1\">\n <UInput\n :model-value=\"state.filterText\"\n class=\"min-w-0 flex-1\"\n icon=\"i-lucide-search\"\n size=\"sm\"\n :placeholder=\"t('tree.filterText.placeholder')\"\n :aria-label=\"t('tree.filterText.placeholder')\"\n @update:model-value=\"model.setFilterText($event)\"\n @keydown.esc=\"model.setFilterText('')\"\n >\n <template v-if=\"state.filterText\" #trailing>\n <UButton\n icon=\"i-lucide-x\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"link\"\n :aria-label=\"t('tree.filterText.clear')\"\n @click=\"model.setFilterText('')\"\n />\n </template>\n </UInput>\n <!-- A popover, not a dropdown menu: a menu item cannot show a dot in an arbitrary CSS colour. -->\n <UPopover\n v-if=\"showBasketFilter\"\n :content=\"{ side: 'bottom', align: 'end', sideOffset: 4, onCloseAutoFocus: (event: Event) => event.preventDefault() }\"\n :ui=\"{ content: 'w-44 p-1' }\"\n >\n <UButton\n icon=\"i-lucide-paint-bucket\"\n size=\"xs\"\n :color=\"shownBaskets.length ? 'primary' : 'neutral'\"\n :variant=\"shownBaskets.length ? 'soft' : 'ghost'\"\n :title=\"t('tree.basketFilter.label')\"\n :aria-label=\"t('tree.basketFilter.label')\"\n />\n <template #content>\n <div role=\"menu\" :aria-label=\"t('tree.basketFilter.label')\">\n <button\n v-for=\"basket in state.baskets\"\n :key=\"basket.id\"\n type=\"button\"\n role=\"menuitemcheckbox\"\n :aria-checked=\"shownBaskets.includes(basket.id)\"\n :class=\"basketOption\"\n @click=\"model.setBasketShown(basket.id, !shownBaskets.includes(basket.id))\"\n >\n <span class=\"size-2.5 shrink-0 rounded-full\" :style=\"{ backgroundColor: basket.color }\" />\n <span class=\"truncate\">{{ basket.label }}</span>\n <UIcon v-if=\"shownBaskets.includes(basket.id)\" name=\"i-lucide-check\" class=\"ml-auto size-3.5 shrink-0\" />\n </button>\n <button v-if=\"shownBaskets.length\" type=\"button\" role=\"menuitem\" :class=\"basketOption\" @click=\"model.clearBasketFilter()\">\n <UIcon name=\"i-lucide-filter-x\" class=\"size-3.5 shrink-0 text-[var(--sk-muted)]\" />\n <span class=\"truncate\">{{ t('tree.basketFilter.reset') }}</span>\n </button>\n </div>\n </template>\n </UPopover>\n </div>\n\n <div class=\"flex items-center justify-end\">\n <UTooltip :text=\"canUseFileRoot ? '' : t('tree.root.unavailable')\">\n <!-- A disabled button swallows pointer events, so the tooltip hangs on the wrapper. -->\n <span class=\"inline-flex\">\n <UButton\n :icon=\"state.virtual ? 'i-lucide-file-stack' : 'i-lucide-folder-tree'\"\n :label=\"t(state.virtual ? 'tree.root.file' : 'tree.root.folders')\"\n size=\"xs\"\n color=\"neutral\"\n :variant=\"state.virtual ? 'soft' : 'ghost'\"\n :disabled=\"!canUseFileRoot\"\n :aria-pressed=\"state.virtual\"\n @click=\"toggleTreeRoot\"\n />\n </span>\n </UTooltip>\n </div>\n\n <div v-if=\"state.view.hasKindFilters\" class=\"flex items-center\">\n <UButton\n icon=\"i-lucide-filter-x\"\n :label=\"t('tree.filter.reset')\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"subtle\"\n @click=\"model.clearAllFilters\"\n />\n </div>\n\n <div v-if=\"state.creatingFolder\" class=\"flex flex-col gap-1\">\n <UInput\n :model-value=\"state.creatingFolder.draft\"\n size=\"sm\"\n autofocus\n :color=\"state.creatingFolder.error ? 'error' : undefined\"\n :highlight=\"!!state.creatingFolder.error\"\n :placeholder=\"state.creatingFolder.parent ? t('tree.folder.placeholderIn', { parent: state.creatingFolder.parent }) : t('tree.folder.placeholder')\"\n :aria-label=\"t('tree.folder.name')\"\n :aria-invalid=\"!!state.creatingFolder.error\"\n @update:model-value=\"model.setFolderDraft($event)\"\n @keydown.enter=\"model.commitCreateFolder\"\n @keydown.esc=\"model.cancelCreateFolder\"\n @blur=\"model.cancelCreateFolder\"\n />\n <p v-if=\"state.creatingFolder.error\" class=\"px-0.5 text-xs text-[var(--sk-danger)]\" role=\"alert\">\n {{ state.creatingFolder.error }}\n </p>\n </div>\n\n <div v-if=\"breadcrumb.crumbs.length\" class=\"flex flex-wrap items-center gap-0.5 text-xs\">\n <UButton\n icon=\"i-lucide-corner-up-left\"\n :label=\"breadcrumb.document?.label ?? t('tree.focus.root')\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"ghost\"\n :title=\"t('tree.focus.leave')\"\n :aria-label=\"t('tree.focus.leave')\"\n @click=\"model.defocus()\"\n />\n <template v-for=\"(crumb, index) in breadcrumb.crumbs\" :key=\"crumb.key\">\n <UIcon name=\"i-lucide-chevron-right\" class=\"size-3 shrink-0 text-[var(--sk-muted)]\" />\n <span v-if=\"index === breadcrumb.crumbs.length - 1\" class=\"min-w-0 truncate px-1.5 font-medium\" aria-current=\"location\">{{ crumb.label }}</span>\n <UButton v-else :label=\"crumb.label\" size=\"xs\" color=\"neutral\" variant=\"ghost\" @click=\"model.focus(crumb.key)\" />\n </template>\n </div>\n\n <p v-if=\"!state.tree && !state.virtual\" class=\"px-2.5 py-1.5 text-sm text-[var(--sk-muted)]\">\n {{ state.tree === null ? t('tree.loadFailed') : t('tree.loading') }}\n </p>\n\n <UTree\n v-else\n :expanded=\"state.view.open\"\n :items=\"state.view.items\"\n :get-key=\"(item: TreeItem) => item.key\"\n size=\"sm\"\n :aria-label=\"t('tree.aria')\"\n @update:expanded=\"onExpanded\"\n >\n <template #item-wrapper=\"{ item }: { item: TreeItem }\">\n <div class=\"w-full\">\n <div\n class=\"group flex w-full cursor-pointer items-center gap-1.5 rounded-md px-2 py-1 text-sm select-none hover:bg-[color-mix(in_oklab,var(--sk-bg-elevated)_60%,transparent)]\"\n :class=\"[\n row(item).active && 'bg-[color-mix(in_oklab,var(--sk-primary)_10%,transparent)] font-medium text-[var(--sk-primary)]',\n row(item).selected && 'bg-[var(--sk-bg-elevated)]',\n row(item).drop?.ok && 'ring-1 ring-[var(--sk-primary)]',\n refusal(item) && 'bg-[color-mix(in_oklab,var(--sk-danger)_10%,transparent)] ring-1 ring-[var(--sk-danger)]',\n row(item).dimmed && 'opacity-60',\n ]\"\n :draggable=\"model.isDraggable(item)\"\n @dragstart=\"onDragStart($event, item)\"\n @dragover=\"onDragOver($event, item)\"\n @dragleave=\"onDragLeave($event, item)\"\n @drop=\"onDrop($event, item)\"\n @dragend=\"model.dragEnd\"\n >\n <UIcon\n v-if=\"row(item).expandable\"\n name=\"i-lucide-chevron-right\"\n class=\"size-3.5 shrink-0 text-[var(--sk-muted)] transition-transform\"\n :class=\"row(item).open && 'rotate-90'\"\n @click=\"onChevronClick($event, item)\"\n />\n <span v-else-if=\"item.type === 'composite' || item.type === 'heading'\" class=\"size-3.5 shrink-0\" />\n <UIcon :name=\"item.icon\" class=\"size-4 shrink-0\" :style=\"item.tone ? { color: item.tone } : undefined\" />\n\n <div v-if=\"row(item).renaming\" class=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <UInput\n :model-value=\"row(item).renaming!.draft\"\n size=\"xs\"\n class=\"min-w-0\"\n autofocus\n :color=\"row(item).renaming!.error ? 'error' : undefined\"\n :highlight=\"!!row(item).renaming!.error\"\n :aria-label=\"t('tree.folder.name')\"\n :aria-invalid=\"!!row(item).renaming!.error\"\n @update:model-value=\"model.setRenameDraft($event)\"\n @focus=\"($event.target as HTMLInputElement).select()\"\n @click.stop\n @keydown.stop\n @keydown.enter=\"model.commitRename\"\n @keydown.esc=\"model.cancelRename\"\n @blur=\"model.blurRename\"\n />\n <p v-if=\"row(item).renaming!.error\" class=\"text-xs text-[var(--sk-danger)]\" role=\"alert\" @click.stop>\n {{ row(item).renaming!.error }}\n </p>\n </div>\n <template v-else>\n <span\n v-if=\"basketOf(item)\"\n class=\"size-2 shrink-0 rounded-full\"\n :style=\"{ backgroundColor: basketOf(item)!.color }\"\n :title=\"basketOf(item)!.label\"\n :aria-label=\"basketOf(item)!.label\"\n role=\"img\"\n data-basket-dot\n />\n <span class=\"min-w-0 flex-1 truncate\" :class=\"item.labelFromBody && 'italic text-[var(--sk-muted)]'\" :title=\"item.label\" :data-label-from-body=\"item.labelFromBody || undefined\">\n <template v-if=\"labelParts(item)\">{{ labelParts(item)![0] }}<mark class=\"rounded-sm bg-[color-mix(in_oklab,var(--sk-primary)_20%,transparent)] text-inherit\">{{ labelParts(item)![1] }}</mark>{{ labelParts(item)![2] }}</template>\n <template v-else>{{ item.label }}</template>\n </span>\n <UIcon\n v-if=\"item.hasSummary\"\n name=\"i-lucide-text-quote\"\n class=\"size-3.5 shrink-0 text-[var(--sk-muted)]\"\n :title=\"t('tree.summary')\"\n />\n <div class=\"flex shrink-0\" @click.stop>\n <UDropdownMenu v-if=\"row(item).showFilter\" :items=\"filterMenu(item)\" :content=\"{ align: 'start' }\">\n <UButton\n icon=\"i-lucide-filter\"\n size=\"xs\"\n :color=\"row(item).hiddenKinds.length ? 'primary' : 'neutral'\"\n :variant=\"row(item).hiddenKinds.length ? 'soft' : 'ghost'\"\n :class=\"!row(item).hiddenKinds.length && 'opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 data-[state=open]:opacity-100'\"\n :title=\"t('tree.filter.title')\"\n :aria-label=\"t('tree.filter.title')\"\n />\n </UDropdownMenu>\n <div class=\"flex opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 has-data-[state=open]:opacity-100\">\n <UButton\n v-if=\"item.type === 'composite' && item.blockId\"\n icon=\"i-lucide-focus\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"ghost\"\n :title=\"t('tree.focus.enter')\"\n :aria-label=\"t('tree.focus.enter')\"\n @click=\"model.focus(item.key)\"\n />\n <UButton\n v-if=\"model.canMerge(item)\"\n icon=\"i-lucide-merge\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"ghost\"\n :title=\"t('tree.merge')\"\n :aria-label=\"t('tree.merge')\"\n @click=\"model.merge(item)\"\n />\n <UDropdownMenu v-if=\"addMenu(item).length\" :items=\"addMenu(item)\" :content=\"{ align: 'start', onCloseAutoFocus: (event: Event) => event.preventDefault() }\">\n <UButton\n icon=\"i-lucide-plus\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"ghost\"\n :title=\"t('tree.add')\"\n :aria-label=\"t('tree.add')\"\n />\n </UDropdownMenu>\n <slot v-if=\"item.type === 'document'\" name=\"document-actions\" :id=\"item.id!\" :title=\"item.label\" />\n <UButton\n v-if=\"item.type === 'document' || item.type === 'folder'\"\n icon=\"i-lucide-trash-2\"\n size=\"xs\"\n color=\"error\"\n variant=\"ghost\"\n :aria-label=\"t('tree.delete')\"\n loading-auto\n @click=\"model.remove(item)\"\n />\n </div>\n </div>\n </template>\n </div>\n <p v-if=\"refusal(item)\" class=\"px-2 pt-0.5 text-xs text-[var(--sk-danger)]\" role=\"status\">\n {{ refusal(item) }}\n </p>\n </div>\n </template>\n </UTree>\n\n <p v-if=\"state.view.noMatches\" class=\"px-2.5 py-1.5 text-sm text-[var(--sk-muted)]\">\n {{ t('tree.noMatches') }}\n </p>\n <p v-if=\"state.view.isEmpty\" class=\"px-2.5 py-1.5 text-sm text-[var(--sk-muted)]\">\n {{ t('tree.empty') }}\n </p>\n </div>\n</template>\n"],"mappings":"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"}
@@ -190,8 +190,9 @@ function DocumentTree({ store, editor, selectedFolder, onSelectedFolderChange, d
190
190
  "data-basket-dot": true
191
191
  }),
192
192
  /* @__PURE__ */ jsx("span", {
193
- className: "min-w-0 flex-1 truncate",
193
+ className: cn("min-w-0 flex-1 truncate", item.labelFromBody && "italic text-muted-foreground"),
194
194
  title: item.label,
195
+ "data-label-from-body": item.labelFromBody || void 0,
195
196
  children: /* @__PURE__ */ jsx(Label, {
196
197
  label: item.label,
197
198
  match: row.match