@ecsplico/editor 3.4.0 → 3.5.1

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 (98) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/code/CodeSurface.vue.d.ts +3 -3
  3. package/dist/core/abilities.js +1 -0
  4. package/dist/core/abilities.js.map +1 -1
  5. package/dist/core/backend-capability.d.ts +1 -1
  6. package/dist/core/backend-capability.js +2 -1
  7. package/dist/core/backend-capability.js.map +1 -1
  8. package/dist/core/backend-types.d.ts +25 -0
  9. package/dist/core/backend-types.js.map +1 -1
  10. package/dist/core/backend.d.ts +9 -1
  11. package/dist/core/backend.js.map +1 -1
  12. package/dist/core/http-backend.js +17 -0
  13. package/dist/core/http-backend.js.map +1 -1
  14. package/dist/core/schema/abilities.d.ts +1 -0
  15. package/dist/core/schema/abilities.js +4 -0
  16. package/dist/core/schema/abilities.js.map +1 -1
  17. package/dist/document/DocumentSurface.vue.d.ts +3 -3
  18. package/dist/features/composites/nodes.js +1 -0
  19. package/dist/features/composites/nodes.js.map +1 -1
  20. package/dist/features/image-studio/components/ImageStudioChat.js +121 -0
  21. package/dist/features/image-studio/components/ImageStudioChat.js.map +1 -0
  22. package/dist/features/image-studio/components/ImageStudioDialog.js +371 -0
  23. package/dist/features/image-studio/components/ImageStudioDialog.js.map +1 -0
  24. package/dist/features/image-studio/components/ImageStudioDialog.vue.d.ts +25 -0
  25. package/dist/features/image-studio/components/ImageStudioTile.js +74 -0
  26. package/dist/features/image-studio/components/ImageStudioTile.js.map +1 -0
  27. package/dist/features/image-studio/components/ImageStudioView.js +117 -0
  28. package/dist/features/image-studio/components/ImageStudioView.js.map +1 -0
  29. package/dist/features/image-studio/feature.d.ts +24 -0
  30. package/dist/features/image-studio/feature.js +53 -0
  31. package/dist/features/image-studio/feature.js.map +1 -0
  32. package/dist/features/image-studio/index.d.ts +5 -0
  33. package/dist/features/image-studio/index.js +4 -0
  34. package/dist/features/image-studio/node-core.d.ts +46 -0
  35. package/dist/features/image-studio/node-core.js +79 -0
  36. package/dist/features/image-studio/node-core.js.map +1 -0
  37. package/dist/features/image-studio/node-store.d.ts +19 -0
  38. package/dist/features/image-studio/node-store.js +35 -0
  39. package/dist/features/image-studio/node-store.js.map +1 -0
  40. package/dist/features/image-studio/react/ImageStudioChat.js +101 -0
  41. package/dist/features/image-studio/react/ImageStudioChat.js.map +1 -0
  42. package/dist/features/image-studio/react/ImageStudioDialog.d.ts +19 -0
  43. package/dist/features/image-studio/react/ImageStudioDialog.js +357 -0
  44. package/dist/features/image-studio/react/ImageStudioDialog.js.map +1 -0
  45. package/dist/features/image-studio/react/ImageStudioTile.js +51 -0
  46. package/dist/features/image-studio/react/ImageStudioTile.js.map +1 -0
  47. package/dist/features/image-studio/react/ImageStudioView.js +126 -0
  48. package/dist/features/image-studio/react/ImageStudioView.js.map +1 -0
  49. package/dist/features/image-studio/react/store.d.ts +37 -0
  50. package/dist/features/image-studio/react/store.js +46 -0
  51. package/dist/features/image-studio/react/store.js.map +1 -0
  52. package/dist/features/image-studio/react.d.ts +18 -0
  53. package/dist/features/image-studio/react.js +21 -0
  54. package/dist/features/image-studio/react.js.map +1 -0
  55. package/dist/features/image-studio/schema.d.ts +29 -0
  56. package/dist/features/image-studio/schema.js +86 -0
  57. package/dist/features/image-studio/schema.js.map +1 -0
  58. package/dist/features/image-studio/state.d.ts +52 -0
  59. package/dist/features/image-studio/state.js +147 -0
  60. package/dist/features/image-studio/state.js.map +1 -0
  61. package/dist/features/image-studio/store.d.ts +29 -0
  62. package/dist/features/image-studio/vue.d.ts +17 -0
  63. package/dist/features/image-studio/vue.js +20 -0
  64. package/dist/features/image-studio/vue.js.map +1 -0
  65. package/dist/features/media/schema.d.ts +5 -1
  66. package/dist/features/media/schema.js +3 -3
  67. package/dist/features/media/schema.js.map +1 -1
  68. package/dist/features/transcript/segment-node.js +1 -0
  69. package/dist/features/transcript/segment-node.js.map +1 -1
  70. package/dist/i18n/de/image-studio.js +39 -0
  71. package/dist/i18n/de/image-studio.js.map +1 -0
  72. package/dist/i18n/de.d.ts +32 -0
  73. package/dist/i18n/de.js +13 -11
  74. package/dist/i18n/de.js.map +1 -1
  75. package/dist/i18n/en/image-studio.js +39 -0
  76. package/dist/i18n/en/image-studio.js.map +1 -0
  77. package/dist/i18n/en.js +13 -11
  78. package/dist/i18n/en.js.map +1 -1
  79. package/dist/markdown/MarkdownSurface.vue.d.ts +9 -9
  80. package/dist/nuxt/module.d.ts +64 -0
  81. package/dist/server/handlers/ai.d.ts +2 -2
  82. package/dist/server/handlers/images.d.ts +22 -0
  83. package/dist/server/handlers/images.js +46 -0
  84. package/dist/server/handlers/images.js.map +1 -0
  85. package/dist/server/handlers/index.d.ts +2 -1
  86. package/dist/server/handlers/index.js +2 -1
  87. package/dist/server/index.d.ts +2 -2
  88. package/dist/server/ports.d.ts +11 -1
  89. package/dist/server/ports.js +2 -1
  90. package/dist/server/ports.js.map +1 -1
  91. package/dist/server/routes.js +19 -0
  92. package/dist/server/routes.js.map +1 -1
  93. package/dist/tree/tree-host.js +1 -1
  94. package/dist/tree/tree-items.js +1 -1
  95. package/dist/vue/AdvancedEditor.vue.d.ts +6 -6
  96. package/dist/vue/SimpleEditor.vue.d.ts +6 -6
  97. package/dist/vue/components/EditorSurface.vue.d.ts +6 -6
  98. package/package.json +21 -1
@@ -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"}
@@ -1,9 +1,9 @@
1
1
  import { findBlockById } from "../core/schema/block-ids.js";
2
2
  import { compositeBasketsOf, compositeJSON, compositeKindsOf } from "../features/composites/nodes.js";
3
+ import { revealBlockInEditor } from "../outline/heading-outline.js";
3
4
  import { pickFiles } from "../features/media/upload.js";
4
5
  import { compositeFocusOf } from "../features/composites/focus.js";
5
6
  import { moveBlockInto, moveIntoVerdict } from "../features/composites/outline.js";
6
- import { revealBlockInEditor } from "../outline/heading-outline.js";
7
7
  import { readOutline } from "./tree-items.js";
8
8
  //#region src/tree/tree-host.ts
9
9
  /** The model's view of `editor`: its Baustein kinds, outline and move verdicts (worded in the language of `t`), read live. Create it once per editor. */
@@ -1,7 +1,7 @@
1
1
  import { compositeKind, defaultCompositeKinds, kindAccepts } from "../features/composites/kinds.js";
2
2
  import { basketById } from "../features/composites/baskets.js";
3
- import { compositeOutline } from "../features/composites/outline.js";
4
3
  import { headingOutline } from "../outline/heading-outline.js";
4
+ import { compositeOutline } from "../features/composites/outline.js";
5
5
  import { folderKey } from "./folder-paths.js";
6
6
  //#region src/tree/tree-items.ts
7
7
  /** Key of the virtual-mode root item (the open document). */
@@ -71,21 +71,21 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
71
71
  editor: import("vue").ComputedRef<Editor | undefined>;
72
72
  preset: ComposedEditor<Component>;
73
73
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
74
- "update:modelValue": (content: JSONContent) => any;
75
- ready: (editor: Editor) => any;
76
- settingsOpen: (tab?: string | undefined) => any;
77
74
  "update:sidebarOpen": (value: boolean) => any;
78
75
  "update:commentsOpen": (value: boolean) => any;
76
+ ready: (editor: Editor) => any;
77
+ "update:modelValue": (content: JSONContent) => any;
79
78
  metadataCreate: (list: string, item: MetadataItem) => any;
80
79
  contentError: (error: Error) => any;
80
+ settingsOpen: (tab?: string | undefined) => any;
81
81
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
82
- "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
83
- onReady?: ((editor: Editor) => any) | undefined;
84
- onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
85
82
  "onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
86
83
  "onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
84
+ onReady?: ((editor: Editor) => any) | undefined;
85
+ "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
87
86
  onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
88
87
  onContentError?: ((error: Error) => any) | undefined;
88
+ onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
89
89
  }>, {
90
90
  content: ContentLevel | readonly FeatureId[];
91
91
  ai: boolean;
@@ -41,21 +41,21 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
41
41
  editor: import("vue").ComputedRef<Editor | undefined>;
42
42
  preset: EditorPreset<Component>;
43
43
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
44
- "update:modelValue": (content: JSONContent) => any;
45
- ready: (editor: Editor) => any;
46
- settingsOpen: (tab?: string | undefined) => any;
47
44
  "update:sidebarOpen": (value: boolean) => any;
48
45
  "update:commentsOpen": (value: boolean) => any;
46
+ ready: (editor: Editor) => any;
47
+ "update:modelValue": (content: JSONContent) => any;
49
48
  metadataCreate: (list: string, item: MetadataItem) => any;
50
49
  contentError: (error: Error) => any;
50
+ settingsOpen: (tab?: string | undefined) => any;
51
51
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
52
- "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
53
- onReady?: ((editor: Editor) => any) | undefined;
54
- onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
55
52
  "onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
56
53
  "onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
54
+ onReady?: ((editor: Editor) => any) | undefined;
55
+ "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
57
56
  onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
58
57
  onContentError?: ((error: Error) => any) | undefined;
58
+ onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
59
59
  }>, {
60
60
  editable: boolean;
61
61
  settingsUi: "internal" | "external";
@@ -43,21 +43,21 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
43
43
  settingsOpen: import("vue").Ref<boolean, boolean>;
44
44
  settingsTab: import("vue").Ref<string | undefined, string | undefined>;
45
45
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
46
- "update:modelValue": (content: JSONContent) => any;
47
- ready: (editor: Editor) => any;
48
- settingsOpen: (tab?: string | undefined) => any;
49
46
  "update:sidebarOpen": (value: boolean) => any;
50
47
  "update:commentsOpen": (value: boolean) => any;
48
+ ready: (editor: Editor) => any;
49
+ "update:modelValue": (content: JSONContent) => any;
51
50
  metadataCreate: (list: string, item: MetadataItem) => any;
52
51
  contentError: (error: Error) => any;
52
+ settingsOpen: (tab?: string | undefined) => any;
53
53
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
54
- "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
55
- onReady?: ((editor: Editor) => any) | undefined;
56
- onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
57
54
  "onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
58
55
  "onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
56
+ onReady?: ((editor: Editor) => any) | undefined;
57
+ "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
59
58
  onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
60
59
  onContentError?: ((error: Error) => any) | undefined;
60
+ onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
61
61
  }>, {
62
62
  editable: boolean;
63
63
  settingsUi: "internal" | "external";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecsplico/editor",
3
- "version": "3.4.0",
3
+ "version": "3.5.1",
4
4
  "type": "module",
5
5
  "description": "Vue and React editor library on TipTap v3 (Vue: Nuxt UI; React: shadcn/ui): framework-free features with Vue and React bindings, simple and advanced presets, tree-shakeable entries. Built ESM with type declarations.",
6
6
  "license": "MIT",
@@ -174,6 +174,26 @@
174
174
  "types": "./dist/features/media/schema.d.ts",
175
175
  "import": "./dist/features/media/schema.js"
176
176
  },
177
+ "./image-studio": {
178
+ "skaile-source": "./src/features/image-studio/index.ts",
179
+ "types": "./dist/features/image-studio/index.d.ts",
180
+ "import": "./dist/features/image-studio/index.js"
181
+ },
182
+ "./image-studio/vue": {
183
+ "skaile-source": "./src/features/image-studio/vue.ts",
184
+ "types": "./dist/features/image-studio/vue.d.ts",
185
+ "import": "./dist/features/image-studio/vue.js"
186
+ },
187
+ "./image-studio/react": {
188
+ "skaile-source": "./src/features/image-studio/react.ts",
189
+ "types": "./dist/features/image-studio/react.d.ts",
190
+ "import": "./dist/features/image-studio/react.js"
191
+ },
192
+ "./image-studio/schema": {
193
+ "skaile-source": "./src/features/image-studio/schema.ts",
194
+ "types": "./dist/features/image-studio/schema.d.ts",
195
+ "import": "./dist/features/image-studio/schema.js"
196
+ },
177
197
  "./mentions": {
178
198
  "skaile-source": "./src/features/mentions/index.ts",
179
199
  "types": "./dist/features/mentions/index.d.ts",