@ecsplico/editor 2.1.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -1
- package/README.md +39 -156
- package/dist/adapters/comments/drizzle.d.ts +1 -1
- package/dist/adapters/comments/{client.js → rest-model.js} +3 -3
- package/dist/adapters/comments/rest-model.js.map +1 -0
- package/dist/adapters/comments/types.d.ts +2 -2
- package/dist/adapters/document-state.d.ts +23 -0
- package/dist/adapters/document-state.js +66 -0
- package/dist/adapters/document-state.js.map +1 -0
- package/dist/adapters/documents.js +14 -11
- package/dist/adapters/documents.js.map +1 -1
- package/dist/adapters/drizzle.d.ts +2 -2
- package/dist/adapters/drizzle.js +97 -88
- package/dist/adapters/drizzle.js.map +1 -1
- package/dist/adapters/forge.d.ts +12 -7
- package/dist/adapters/forge.js +38 -45
- package/dist/adapters/forge.js.map +1 -1
- package/dist/adapters/http.d.ts +20 -0
- package/dist/adapters/http.js +68 -0
- package/dist/adapters/http.js.map +1 -0
- package/dist/adapters/index.d.ts +5 -5
- package/dist/adapters/json.d.ts +3 -3
- package/dist/adapters/json.js +58 -49
- package/dist/adapters/json.js.map +1 -1
- package/dist/adapters/memory.d.ts +21 -0
- package/dist/adapters/memory.js +53 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/nitro.d.ts +23 -20
- package/dist/adapters/nitro.js +58 -163
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/react.d.ts +55 -0
- package/dist/adapters/react.js +209 -0
- package/dist/adapters/react.js.map +1 -0
- package/dist/adapters/shared.d.ts +15 -21
- package/dist/adapters/shared.js +10 -8
- package/dist/adapters/shared.js.map +1 -1
- package/dist/adapters/vue.d.ts +15 -30
- package/dist/adapters/vue.js +36 -87
- package/dist/adapters/vue.js.map +1 -1
- package/dist/config/index.d.ts +105 -0
- package/dist/config/index.js +95 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/abilities.js +1 -0
- package/dist/core/abilities.js.map +1 -1
- package/dist/{adapters/routes.js → core/api-routes.js} +5 -3
- package/dist/core/api-routes.js.map +1 -0
- package/dist/core/backend-capability.d.ts +7 -0
- package/dist/core/backend-capability.js +17 -0
- package/dist/core/backend-capability.js.map +1 -0
- package/dist/core/backend-types.d.ts +245 -0
- package/dist/core/backend-types.js +17 -0
- package/dist/core/backend-types.js.map +1 -0
- package/dist/core/backend.d.ts +108 -0
- package/dist/core/backend.js +14 -0
- package/dist/core/backend.js.map +1 -0
- package/dist/core/block-convert.d.ts +7 -5
- package/dist/core/block-convert.js +28 -5
- package/dist/core/block-convert.js.map +1 -1
- package/dist/core/command-types.d.ts +89 -0
- package/dist/core/commands.d.ts +40 -0
- package/dist/core/commands.js +151 -0
- package/dist/core/commands.js.map +1 -0
- package/dist/core/context.d.ts +17 -20
- package/dist/core/context.js +24 -21
- package/dist/core/context.js.map +1 -1
- package/dist/core/core-feature.d.ts +22 -7
- package/dist/core/core-feature.js +51 -18
- package/dist/core/core-feature.js.map +1 -1
- package/dist/core/define-editor.d.ts +4 -3
- package/dist/core/define-editor.js +14 -7
- package/dist/core/define-editor.js.map +1 -1
- package/dist/core/document-markdown.js +12 -3
- package/dist/core/document-markdown.js.map +1 -1
- package/dist/core/editor-ui.d.ts +8 -1
- package/dist/core/editor-ui.js +9 -8
- package/dist/core/editor-ui.js.map +1 -1
- package/dist/core/extension-points.d.ts +3 -15
- package/dist/core/feature-context.d.ts +8 -7
- package/dist/core/feature-context.js +12 -11
- package/dist/core/feature-context.js.map +1 -1
- package/dist/core/feature.d.ts +35 -19
- package/dist/core/feature.js.map +1 -1
- package/dist/core/fences.d.ts +2 -1
- package/dist/core/fences.js +3 -2
- package/dist/core/fences.js.map +1 -1
- package/dist/core/fetch-error.d.ts +1 -1
- package/dist/core/fetch-error.js +3 -1
- package/dist/core/fetch-error.js.map +1 -1
- package/dist/core/hash.d.ts +5 -0
- package/dist/core/hash.js +11 -0
- package/dist/core/hash.js.map +1 -0
- package/dist/core/http-backend.d.ts +11 -0
- package/dist/core/http-backend.js +195 -0
- package/dist/core/http-backend.js.map +1 -0
- package/dist/core/index.d.ts +14 -9
- package/dist/core/index.js +12 -9
- package/dist/core/palette.d.ts +48 -0
- package/dist/core/palette.js +95 -0
- package/dist/core/palette.js.map +1 -0
- package/dist/core/schema/abilities.d.ts +2 -1
- package/dist/core/schema/abilities.js +5 -1
- package/dist/core/schema/abilities.js.map +1 -1
- package/dist/core/schema/index.d.ts +11 -5
- package/dist/core/schema/index.js +13 -8
- package/dist/core/schema/index.js.map +1 -1
- package/dist/core/schema/mark-flags.d.ts +8 -1
- package/dist/core/schema/of-features.d.ts +13 -0
- package/dist/core/schema/of-features.js +14 -0
- package/dist/core/schema/of-features.js.map +1 -0
- package/dist/core/slash.d.ts +8 -6
- package/dist/core/slash.js +24 -16
- package/dist/core/slash.js.map +1 -1
- package/dist/core/surface-session.js +13 -5
- package/dist/core/surface-session.js.map +1 -1
- package/dist/core/toolbar-items.d.ts +10 -4
- package/dist/core/toolbar-items.js +269 -195
- package/dist/core/toolbar-items.js.map +1 -1
- package/dist/core/util.js +11 -0
- package/dist/core/util.js.map +1 -0
- package/dist/document/DocumentSurface.d.ts +1 -1
- package/dist/features/ai/agent-op-review.d.ts +1 -1
- package/dist/features/ai/agent-op-review.js +1 -1
- package/dist/features/ai/agent-ops.js +1 -1
- package/dist/features/ai/ai-apply.d.ts +2 -2
- package/dist/features/ai/ai-apply.js +1 -1
- package/dist/features/ai/ai-review.js +1 -1
- package/dist/features/ai/ai-sse.d.ts +6 -6
- package/dist/features/ai/ai-sse.js +20 -63
- package/dist/features/ai/ai-sse.js.map +1 -1
- package/dist/features/ai/assistant.d.ts +8 -7
- package/dist/features/ai/assistant.js +3 -3
- package/dist/features/ai/assistant.js.map +1 -1
- package/dist/features/ai/block-actions.d.ts +3 -2
- package/dist/features/ai/block-actions.js +3 -2
- package/dist/features/ai/block-actions.js.map +1 -1
- package/dist/features/ai/components/AiSelectionMenu.js +1 -1
- package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/components/DocumentChatOps.vue.d.ts +1 -1
- package/dist/features/ai/components/SettingsModelProfiles.js +8 -8
- package/dist/features/ai/components/SettingsModelProfiles.js.map +1 -1
- package/dist/features/ai/document-chat-model.d.ts +8 -7
- package/dist/features/ai/document-chat-model.js +5 -5
- package/dist/features/ai/document-chat-model.js.map +1 -1
- package/dist/features/ai/document-chat.js +1 -1
- package/dist/features/ai/feature.js +27 -14
- package/dist/features/ai/feature.js.map +1 -1
- package/dist/features/ai/ghost-autocomplete.d.ts +7 -6
- package/dist/features/ai/ghost-autocomplete.js +6 -9
- package/dist/features/ai/ghost-autocomplete.js.map +1 -1
- package/dist/features/ai/index.d.ts +4 -3
- package/dist/features/ai/index.js +10 -10
- package/dist/features/ai/model-profiles.d.ts +5 -4
- package/dist/features/ai/model-profiles.js +2 -2
- package/dist/features/ai/model-profiles.js.map +1 -1
- package/dist/features/ai/protocol/block-lines.js +1 -1
- package/dist/features/ai/protocol/block-lines.js.map +1 -1
- package/dist/features/ai/protocol/chat.js +4 -1
- package/dist/features/ai/protocol/chat.js.map +1 -1
- package/dist/features/ai/protocol/index.d.ts +3 -2
- package/dist/features/ai/protocol/models.d.ts +2 -16
- package/dist/features/ai/protocol/models.js.map +1 -1
- package/dist/features/ai/rate-limit.d.ts +1 -1
- package/dist/features/ai/rate-limit.js +3 -1
- package/dist/features/ai/rate-limit.js.map +1 -1
- package/dist/features/ai/react/AiSelectionMenu.js +2 -2
- package/dist/features/ai/react/AiSidebar.js +4 -4
- package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/react/DocumentChatOps.d.ts +1 -1
- package/dist/features/ai/react/DocumentChatOps.js +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.js +8 -10
- package/dist/features/ai/react/SettingsModelProfiles.js.map +1 -1
- package/dist/features/ai/react/use-ai-assistant.d.ts +1 -1
- package/dist/features/ai/react/use-ai-assistant.js +3 -3
- package/dist/features/ai/react/use-ai-assistant.js.map +1 -1
- package/dist/features/ai/react/use-document-chat.js +2 -2
- package/dist/features/ai/react/use-document-chat.js.map +1 -1
- package/dist/features/ai/react/use-model-profiles.d.ts +2 -1
- package/dist/features/ai/selection-flow.d.ts +1 -1
- package/dist/features/ai/selection-flow.js +1 -1
- package/dist/features/ai/sidebar-flow.d.ts +1 -1
- package/dist/features/ai/sidebar-flow.js +1 -1
- package/dist/features/ai/use-ai-assistant.d.ts +1 -1
- package/dist/features/ai/use-ai-assistant.js +3 -3
- package/dist/features/ai/use-ai-assistant.js.map +1 -1
- package/dist/features/ai/use-document-chat.js +2 -2
- package/dist/features/ai/use-document-chat.js.map +1 -1
- package/dist/features/ai/use-model-profiles.d.ts +2 -1
- package/dist/features/authors/attribution.d.ts +1 -1
- package/dist/features/authors/feature.js +12 -11
- package/dist/features/authors/feature.js.map +1 -1
- package/dist/features/block-ui/block-actions.d.ts +1 -1
- package/dist/features/block-ui/block-actions.js +1 -1
- package/dist/features/block-ui/block-actions.js.map +1 -1
- package/dist/features/block-ui/components/BlockUi.js +1 -1
- package/dist/features/block-ui/feature.js +1 -0
- package/dist/features/block-ui/feature.js.map +1 -1
- package/dist/features/block-ui/react/BlockUi.js +1 -1
- package/dist/features/collab/anchor-resolver.js +1 -1
- package/dist/features/collab/feature.js +2 -0
- package/dist/features/collab/feature.js.map +1 -1
- package/dist/features/comments/components/CommentThreadView.vue.d.ts +2 -1
- package/dist/features/comments/feature.js +12 -11
- package/dist/features/comments/feature.js.map +1 -1
- package/dist/features/comments/index.d.ts +3 -2
- package/dist/features/comments/index.js +1 -1
- package/dist/features/comments/margin-layout.d.ts +3 -2
- package/dist/features/comments/model.d.ts +3 -67
- package/dist/features/comments/model.js +2 -2
- package/dist/features/comments/model.js.map +1 -1
- package/dist/features/comments/react/CommentThreadView.d.ts +3 -2
- package/dist/features/comments/react/use-comments.d.ts +2 -1
- package/dist/features/comments/react.d.ts +1 -1
- package/dist/features/comments/schema.d.ts +2 -52
- package/dist/features/comments/schema.js.map +1 -1
- package/dist/features/comments/snapshot-equality.js.map +1 -1
- package/dist/features/comments/use-comments.d.ts +3 -2
- package/dist/features/comments/vue.d.ts +1 -1
- package/dist/features/composites/accepted-children.d.ts +1 -0
- package/dist/features/composites/accepted-children.js +16 -1
- package/dist/features/composites/accepted-children.js.map +1 -1
- package/dist/features/composites/ai-brief.js +31 -0
- package/dist/features/composites/ai-brief.js.map +1 -1
- package/dist/features/composites/bibliography-command.d.ts +11 -0
- package/dist/features/composites/bibliography-form.js +90 -0
- package/dist/features/composites/bibliography-form.js.map +1 -0
- package/dist/features/composites/bibliography.d.ts +33 -0
- package/dist/features/composites/bibliography.js +120 -0
- package/dist/features/composites/bibliography.js.map +1 -0
- package/dist/features/composites/components/BibliographyEditor.js +261 -0
- package/dist/features/composites/components/BibliographyEditor.js.map +1 -0
- package/dist/features/composites/components/CompositeChrome.js +75 -26
- package/dist/features/composites/components/CompositeChrome.js.map +1 -1
- package/dist/features/composites/components/CompositeDetails.js +46 -11
- package/dist/features/composites/components/CompositeDetails.js.map +1 -1
- package/dist/features/composites/components/CompositeDetails.vue.d.ts +2 -1
- package/dist/features/composites/components/CompositeFieldEditor.js +37 -4
- package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
- package/dist/features/composites/components/CompositeInspector.js +17 -5
- package/dist/features/composites/components/CompositeInspector.js.map +1 -1
- package/dist/features/composites/components/CompositeTable.js +13 -4
- package/dist/features/composites/components/CompositeTable.js.map +1 -1
- package/dist/features/composites/components/CompositeTagsEditor.js +1 -1
- package/dist/features/composites/components/CompositeView.js +11 -1
- package/dist/features/composites/components/CompositeView.js.map +1 -1
- package/dist/features/composites/components/DerivationBadge.js +103 -0
- package/dist/features/composites/components/DerivationBadge.js.map +1 -0
- package/dist/features/composites/components/ReferenceFieldSelect.js +60 -0
- package/dist/features/composites/components/ReferenceFieldSelect.js.map +1 -0
- package/dist/features/composites/components/ReferenceView.js +122 -0
- package/dist/features/composites/components/ReferenceView.js.map +1 -0
- package/dist/features/composites/components/ReferenceView.vue.d.ts +96 -0
- package/dist/features/composites/components/SettingsEditingTab.js +1 -1
- package/dist/features/composites/convert.js +2 -2
- package/dist/features/composites/convert.js.map +1 -1
- package/dist/features/composites/derivation.d.ts +61 -0
- package/dist/features/composites/derivation.js +98 -0
- package/dist/features/composites/derivation.js.map +1 -0
- package/dist/features/composites/derived-chrome.js +69 -0
- package/dist/features/composites/derived-chrome.js.map +1 -0
- package/dist/features/composites/feature.js +159 -109
- package/dist/features/composites/feature.js.map +1 -1
- package/dist/features/composites/fences.d.ts +4 -3
- package/dist/features/composites/fences.js +51 -7
- package/dist/features/composites/fences.js.map +1 -1
- package/dist/features/composites/index.d.ts +12 -3
- package/dist/features/composites/index.js +15 -6
- package/dist/features/composites/inspector-model.d.ts +4 -1
- package/dist/features/composites/inspector-model.js +8 -4
- package/dist/features/composites/inspector-model.js.map +1 -1
- package/dist/features/composites/kinds.d.ts +8 -1
- package/dist/features/composites/kinds.js +145 -6
- package/dist/features/composites/kinds.js.map +1 -1
- package/dist/features/composites/legacy.d.ts +4 -1
- package/dist/features/composites/legacy.js +56 -1
- package/dist/features/composites/legacy.js.map +1 -1
- package/dist/features/composites/meta-display.js +40 -1
- package/dist/features/composites/meta-display.js.map +1 -1
- package/dist/features/composites/meta-keys.d.ts +7 -0
- package/dist/features/composites/meta-keys.js +21 -0
- package/dist/features/composites/meta-keys.js.map +1 -0
- package/dist/features/composites/node-views.d.ts +6 -3
- package/dist/features/composites/node-views.js +7 -3
- package/dist/features/composites/node-views.js.map +1 -1
- package/dist/features/composites/nodes.d.ts +6 -1
- package/dist/features/composites/nodes.js +33 -7
- package/dist/features/composites/nodes.js.map +1 -1
- package/dist/features/composites/react/BibliographyEditor.js +229 -0
- package/dist/features/composites/react/BibliographyEditor.js.map +1 -0
- package/dist/features/composites/react/CompositeChrome.js +65 -17
- package/dist/features/composites/react/CompositeChrome.js.map +1 -1
- package/dist/features/composites/react/CompositeDetails.d.ts +2 -1
- package/dist/features/composites/react/CompositeDetails.js +45 -6
- package/dist/features/composites/react/CompositeDetails.js.map +1 -1
- package/dist/features/composites/react/CompositeFieldEditor.d.ts +1 -1
- package/dist/features/composites/react/CompositeFieldEditor.js +24 -1
- package/dist/features/composites/react/CompositeFieldEditor.js.map +1 -1
- package/dist/features/composites/react/CompositeInspector.js +11 -5
- package/dist/features/composites/react/CompositeInspector.js.map +1 -1
- package/dist/features/composites/react/CompositeStatusMenu.js +1 -1
- package/dist/features/composites/react/CompositeSummaryEditor.js +1 -1
- package/dist/features/composites/react/CompositeTable.js +16 -2
- package/dist/features/composites/react/CompositeTable.js.map +1 -1
- package/dist/features/composites/react/CompositeTagsEditor.js +2 -2
- package/dist/features/composites/react/CompositeView.js +6 -1
- package/dist/features/composites/react/CompositeView.js.map +1 -1
- package/dist/features/composites/react/DerivationBadge.js +116 -0
- package/dist/features/composites/react/DerivationBadge.js.map +1 -0
- package/dist/features/composites/react/ReferenceFieldSelect.js +52 -0
- package/dist/features/composites/react/ReferenceFieldSelect.js.map +1 -0
- package/dist/features/composites/react/ReferenceView.d.ts +11 -0
- package/dist/features/composites/react/ReferenceView.js +115 -0
- package/dist/features/composites/react/ReferenceView.js.map +1 -0
- package/dist/features/composites/react/SettingsEditingTab.js +2 -2
- package/dist/features/composites/react.d.ts +2 -1
- package/dist/features/composites/react.js +7 -3
- package/dist/features/composites/react.js.map +1 -1
- package/dist/features/composites/reference-ai.js +16 -0
- package/dist/features/composites/reference-ai.js.map +1 -0
- package/dist/features/composites/reference-label.d.ts +19 -0
- package/dist/features/composites/reference-label.js +58 -0
- package/dist/features/composites/reference-label.js.map +1 -0
- package/dist/features/composites/reference-suggestion.d.ts +10 -0
- package/dist/features/composites/reference-suggestion.js +41 -0
- package/dist/features/composites/reference-suggestion.js.map +1 -0
- package/dist/features/composites/reference-text.d.ts +18 -0
- package/dist/features/composites/reference-text.js +89 -0
- package/dist/features/composites/reference-text.js.map +1 -0
- package/dist/features/composites/reference.d.ts +44 -0
- package/dist/features/composites/reference.js +148 -0
- package/dist/features/composites/reference.js.map +1 -0
- package/dist/features/composites/reveal-source.d.ts +32 -0
- package/dist/features/composites/reveal-source.js +89 -0
- package/dist/features/composites/reveal-source.js.map +1 -0
- package/dist/features/composites/rows.d.ts +21 -2
- package/dist/features/composites/rows.js +63 -9
- package/dist/features/composites/rows.js.map +1 -1
- package/dist/features/composites/schema.d.ts +9 -3
- package/dist/features/composites/schema.js +11 -4
- package/dist/features/composites/schema.js.map +1 -1
- package/dist/features/composites/table-model.js +12 -21
- package/dist/features/composites/table-model.js.map +1 -1
- package/dist/features/composites/vue.d.ts +3 -2
- package/dist/features/composites/vue.js +8 -4
- package/dist/features/composites/vue.js.map +1 -1
- package/dist/features/media/assets.d.ts +2 -0
- package/dist/features/media/assets.js +3 -2
- package/dist/features/media/assets.js.map +1 -1
- package/dist/features/media/components/FileView.js +13 -4
- package/dist/features/media/components/FileView.js.map +1 -1
- package/dist/features/media/feature.d.ts +3 -3
- package/dist/features/media/feature.js +21 -28
- package/dist/features/media/feature.js.map +1 -1
- package/dist/features/media/file-import.js +2 -2
- package/dist/features/media/file-import.js.map +1 -1
- package/dist/features/media/index.d.ts +2 -2
- package/dist/features/media/index.js +2 -2
- package/dist/features/media/react/FileView.js +19 -2
- package/dist/features/media/react/FileView.js.map +1 -1
- package/dist/features/media/schema.d.ts +8 -0
- package/dist/features/media/schema.js +126 -11
- package/dist/features/media/schema.js.map +1 -1
- package/dist/features/media/upload.d.ts +16 -14
- package/dist/features/media/upload.js +13 -19
- package/dist/features/media/upload.js.map +1 -1
- package/dist/features/mentions/index.d.ts +4 -3
- package/dist/features/mentions/index.js +4 -1
- package/dist/features/mentions/index.js.map +1 -1
- package/dist/features/mentions/mention-text.d.ts +1 -1
- package/dist/features/mentions/mention-text.js.map +1 -1
- package/dist/features/mentions/mention.d.ts +0 -6
- package/dist/features/mentions/mention.js.map +1 -1
- package/dist/features/mentions/metadata-defaults.d.ts +2 -2
- package/dist/features/mentions/metadata-defaults.js +3 -9
- package/dist/features/mentions/metadata-defaults.js.map +1 -1
- package/dist/features/mentions/schema.d.ts +2 -1
- package/dist/features/mentions/schema.js.map +1 -1
- package/dist/features/tables/feature.d.ts +2 -1
- package/dist/features/tables/feature.js +90 -70
- package/dist/features/tables/feature.js.map +1 -1
- package/dist/features/themes/components/SettingsAppearanceTab.js +6 -9
- package/dist/features/themes/components/SettingsAppearanceTab.js.map +1 -1
- package/dist/features/themes/feature.js +2 -0
- package/dist/features/themes/feature.js.map +1 -1
- package/dist/features/themes/index.d.ts +2 -1
- package/dist/features/themes/react/SettingsAppearanceTab.js +7 -10
- package/dist/features/themes/react/SettingsAppearanceTab.js.map +1 -1
- package/dist/features/themes/schema.d.ts +2 -6
- package/dist/features/themes/schema.js.map +1 -1
- package/dist/features/themes/theme-runtime.d.ts +1 -1
- package/dist/features/themes/theme-runtime.js +4 -2
- package/dist/features/themes/theme-runtime.js.map +1 -1
- package/dist/features/transcript/TranscriptSegmentView.js +48 -28
- package/dist/features/transcript/TranscriptSegmentView.js.map +1 -1
- package/dist/features/transcript/feature.js +21 -19
- package/dist/features/transcript/feature.js.map +1 -1
- package/dist/features/transcript/react/TranscriptSegmentView.js +55 -36
- package/dist/features/transcript/react/TranscriptSegmentView.js.map +1 -1
- package/dist/features/transcript/segment-node.d.ts +2 -0
- package/dist/features/transcript/segment-node.js +35 -7
- package/dist/features/transcript/segment-node.js.map +1 -1
- package/dist/i18n/de/composites.js +91 -1
- package/dist/i18n/de/composites.js.map +1 -1
- package/dist/i18n/de/integrations.js +49 -8
- package/dist/i18n/de/integrations.js.map +1 -1
- package/dist/i18n/de/media.js +0 -1
- package/dist/i18n/de/media.js.map +1 -1
- package/dist/i18n/de/palette.js +22 -0
- package/dist/i18n/de/palette.js.map +1 -0
- package/dist/i18n/de/tables.js +0 -1
- package/dist/i18n/de/tables.js.map +1 -1
- package/dist/i18n/de/transcript.js +2 -1
- package/dist/i18n/de/transcript.js.map +1 -1
- package/dist/i18n/de/tree.js +3 -0
- package/dist/i18n/de/tree.js.map +1 -1
- package/dist/i18n/de.d.ts +157 -11
- package/dist/i18n/de.js +3 -1
- package/dist/i18n/de.js.map +1 -1
- package/dist/i18n/en/composites.js +89 -1
- package/dist/i18n/en/composites.js.map +1 -1
- package/dist/i18n/en/integrations.js +49 -8
- package/dist/i18n/en/integrations.js.map +1 -1
- package/dist/i18n/en/media.js +0 -1
- package/dist/i18n/en/media.js.map +1 -1
- package/dist/i18n/en/palette.js +22 -0
- package/dist/i18n/en/palette.js.map +1 -0
- package/dist/i18n/en/tables.js +0 -1
- package/dist/i18n/en/tables.js.map +1 -1
- package/dist/i18n/en/transcript.js +2 -1
- package/dist/i18n/en/transcript.js.map +1 -1
- package/dist/i18n/en/tree.js +3 -0
- package/dist/i18n/en/tree.js.map +1 -1
- package/dist/i18n/en.js +3 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/integrations/composite-ai/bibliography-fetch.d.ts +31 -0
- package/dist/integrations/composite-ai/bibliography-fetch.js +159 -0
- package/dist/integrations/composite-ai/bibliography-fetch.js.map +1 -0
- package/dist/integrations/composite-ai/composite-fill.d.ts +30 -8
- package/dist/integrations/composite-ai/composite-fill.js +97 -56
- package/dist/integrations/composite-ai/composite-fill.js.map +1 -1
- package/dist/integrations/composite-ai/composite-summary.d.ts +4 -12
- package/dist/integrations/composite-ai/composite-summary.js +7 -26
- package/dist/integrations/composite-ai/composite-summary.js.map +1 -1
- package/dist/integrations/composite-ai/derive.d.ts +29 -0
- package/dist/integrations/composite-ai/derive.js +173 -0
- package/dist/integrations/composite-ai/derive.js.map +1 -0
- package/dist/integrations/composite-ai/index.d.ts +11 -7
- package/dist/integrations/composite-ai/index.js +72 -14
- package/dist/integrations/composite-ai/index.js.map +1 -1
- package/dist/integrations/composite-ai/rendition.d.ts +45 -0
- package/dist/integrations/composite-ai/rendition.js +314 -0
- package/dist/integrations/composite-ai/rendition.js.map +1 -0
- package/dist/integrations/composite-media/index.d.ts +7 -5
- package/dist/integrations/composite-media/index.js +86 -13
- package/dist/integrations/composite-media/index.js.map +1 -1
- package/dist/integrations/transcript-ai/index.d.ts +1 -1
- package/dist/integrations/transcript-ai/index.js +5 -2
- package/dist/integrations/transcript-ai/index.js.map +1 -1
- package/dist/integrations/transcript-ai/protocol.d.ts +2 -12
- package/dist/integrations/transcript-ai/protocol.js +0 -2
- package/dist/integrations/transcript-ai/protocol.js.map +1 -1
- package/dist/integrations/transcript-ai/transcript-cleanup.d.ts +5 -5
- package/dist/integrations/transcript-ai/transcript-cleanup.js +7 -10
- package/dist/integrations/transcript-ai/transcript-cleanup.js.map +1 -1
- package/dist/markdown/MarkdownSurface.vue.d.ts +6 -5
- package/dist/markdown/MarkdownSurface2.js +3 -6
- package/dist/markdown/MarkdownSurface2.js.map +1 -1
- package/dist/markdown/document.js +1 -1
- package/dist/markdown/source-mode.d.ts +1 -1
- package/dist/nuxt/module.d.ts +1402 -54
- package/dist/nuxt/module.js +6 -49
- package/dist/nuxt/module.js.map +1 -1
- package/dist/nuxt/runtime/install-context.d.ts +7 -3
- package/dist/nuxt/runtime/install-context.js +14 -8
- package/dist/nuxt/runtime/install-context.js.map +1 -1
- package/dist/nuxt/runtime/plugins/editor-context-nuxt-ui.client.ts +2 -2
- package/dist/nuxt/runtime/server/plugins/editor-server.js +20 -12
- package/dist/nuxt/runtime/server/plugins/editor-server.js.map +1 -1
- package/dist/presets/compose.d.ts +94 -0
- package/dist/presets/compose.js +164 -0
- package/dist/presets/compose.js.map +1 -0
- package/dist/react/AdvancedEditor.d.ts +15 -15
- package/dist/react/AdvancedEditor.js +15 -13
- package/dist/react/AdvancedEditor.js.map +1 -1
- package/dist/react/EditorCommandPalette.d.ts +13 -0
- package/dist/react/EditorCommandPalette.js +118 -0
- package/dist/react/EditorCommandPalette.js.map +1 -0
- package/dist/react/EditorSettingsModal.d.ts +5 -5
- package/dist/react/EditorSettingsModal.js +7 -4
- package/dist/react/EditorSettingsModal.js.map +1 -1
- package/dist/react/EditorSettingsPanel.d.ts +7 -7
- package/dist/react/EditorSettingsPanel.js +8 -5
- package/dist/react/EditorSettingsPanel.js.map +1 -1
- package/dist/react/EditorSurface.d.ts +7 -7
- package/dist/react/EditorSurface.js +10 -6
- package/dist/react/EditorSurface.js.map +1 -1
- package/dist/react/EditorToolbar.d.ts +4 -6
- package/dist/react/EditorToolbar.js +29 -37
- package/dist/react/EditorToolbar.js.map +1 -1
- package/dist/react/SimpleEditor.d.ts +22 -23
- package/dist/react/SimpleEditor.js +21 -7
- package/dist/react/SimpleEditor.js.map +1 -1
- package/dist/react/advanced.d.ts +4 -3
- package/dist/react/advanced.js +2 -2
- package/dist/react/compose.d.ts +20 -0
- package/dist/react/compose.js +49 -0
- package/dist/react/compose.js.map +1 -0
- package/dist/react/composed-surface.d.ts +39 -0
- package/dist/react/composed-surface.js +71 -0
- package/dist/react/composed-surface.js.map +1 -0
- package/dist/react/context.js +6 -2
- package/dist/react/context.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +2 -1
- package/dist/react/key-labels.js +15 -0
- package/dist/react/key-labels.js.map +1 -0
- package/dist/react/settings.d.ts +1 -1
- package/dist/react/simple.d.ts +2 -2
- package/dist/react/ui/command.js +36 -1
- package/dist/react/ui/command.js.map +1 -1
- package/dist/schema/advanced.d.ts +6 -6
- package/dist/schema/advanced.js +26 -29
- package/dist/schema/advanced.js.map +1 -1
- package/dist/server/ai.d.ts +5 -16
- package/dist/server/ai.js.map +1 -1
- package/dist/server/auth.d.ts +9 -0
- package/dist/server/collab-state.d.ts +1 -1
- package/dist/server/collab-state.js.map +1 -1
- package/dist/server/collab.d.ts +13 -13
- package/dist/server/collab.js +34 -18
- package/dist/server/collab.js.map +1 -1
- package/dist/server/crossref.js +78 -0
- package/dist/server/crossref.js.map +1 -0
- package/dist/server/errors.d.ts +1 -1
- package/dist/server/errors.js +1 -1
- package/dist/server/errors.js.map +1 -1
- package/dist/server/extract.js +308 -0
- package/dist/server/extract.js.map +1 -0
- package/dist/{nuxt/runtime/server/utils/editor-route.js → server/h3.js} +8 -16
- package/dist/server/h3.js.map +1 -0
- package/dist/server/handlers/ai.d.ts +30 -9
- package/dist/server/handlers/ai.js +170 -36
- package/dist/server/handlers/ai.js.map +1 -1
- package/dist/server/handlers/context.d.ts +12 -5
- package/dist/server/handlers/context.js +10 -4
- package/dist/server/handlers/context.js.map +1 -1
- package/dist/server/handlers/editor.d.ts +19 -13
- package/dist/server/handlers/editor.js +49 -38
- package/dist/server/handlers/editor.js.map +1 -1
- package/dist/server/handlers/index.d.ts +4 -4
- package/dist/server/handlers/index.js +4 -4
- package/dist/server/host.d.ts +16 -25
- package/dist/server/host.js +58 -41
- package/dist/server/host.js.map +1 -1
- package/dist/server/index.d.ts +6 -4
- package/dist/server/index.js +3 -2
- package/dist/server/pdf.js +97 -0
- package/dist/server/pdf.js.map +1 -0
- package/dist/server/{adapter.d.ts → ports.d.ts} +81 -30
- package/dist/server/ports.js +29 -0
- package/dist/server/ports.js.map +1 -0
- package/dist/server/routes.js +144 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/index.js.map +1 -1
- package/dist/styles/composites.css +25 -0
- package/dist/tree/DocumentTree.js +56 -29
- package/dist/tree/DocumentTree.js.map +1 -1
- package/dist/tree/react/DocumentTree.js +23 -0
- package/dist/tree/react/DocumentTree.js.map +1 -1
- package/dist/tree/tree-host.js +2 -2
- package/dist/tree/tree-items.js +1 -1
- package/dist/vue/AdvancedEditor.js +30 -9
- package/dist/vue/AdvancedEditor.js.map +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +28 -8
- package/dist/vue/SimpleEditor.js +62 -16
- package/dist/vue/SimpleEditor.js.map +1 -1
- package/dist/vue/SimpleEditor.vue.d.ts +37 -7
- package/dist/vue/advanced.d.ts +3 -2
- package/dist/vue/advanced.js +2 -2
- package/dist/vue/components/EditorCommandPalette.js +91 -0
- package/dist/vue/components/EditorCommandPalette.js.map +1 -0
- package/dist/vue/components/EditorCommandPalette.vue.d.ts +15 -0
- package/dist/vue/components/EditorSettingsModal.js +10 -6
- package/dist/vue/components/EditorSettingsModal.js.map +1 -1
- package/dist/vue/components/EditorSettingsModal.vue.d.ts +4 -4
- package/dist/vue/components/EditorSettingsPanel.js +10 -6
- package/dist/vue/components/EditorSettingsPanel.js.map +1 -1
- package/dist/vue/components/EditorSettingsPanel.vue.d.ts +6 -6
- package/dist/vue/components/EditorSurface.js +11 -6
- package/dist/vue/components/EditorSurface.js.map +1 -1
- package/dist/vue/components/EditorSurface.vue.d.ts +7 -7
- package/dist/vue/components/EditorToolbar.js +41 -49
- package/dist/vue/components/EditorToolbar.js.map +1 -1
- package/dist/vue/components/EditorToolbar.vue.d.ts +2 -4
- package/dist/vue/compose.d.ts +17 -0
- package/dist/vue/compose.js +51 -0
- package/dist/vue/compose.js.map +1 -0
- package/dist/vue/context-key.d.ts +6 -0
- package/dist/vue/context-key.js +6 -0
- package/dist/vue/context-key.js.map +1 -0
- package/dist/vue/context.d.ts +8 -6
- package/dist/vue/context.js +6 -5
- package/dist/vue/context.js.map +1 -1
- package/dist/vue/index.d.ts +10 -8
- package/dist/vue/index.js +10 -8
- package/dist/vue/kit/index.d.ts +2 -2
- package/dist/vue/kit/nuxt-ui.js +23 -0
- package/dist/vue/kit/nuxt-ui.js.map +1 -1
- package/dist/vue/kit/types.d.ts +40 -1
- package/package.json +30 -16
- package/dist/adapters/comments/client.d.ts +0 -64
- package/dist/adapters/comments/client.js.map +0 -1
- package/dist/adapters/routes.js.map +0 -1
- package/dist/core/toolbar.d.ts +0 -74
- package/dist/core/toolbar.js +0 -45
- package/dist/core/toolbar.js.map +0 -1
- package/dist/integrations/composite-ai/document-transcribe.d.ts +0 -14
- package/dist/integrations/composite-ai/document-transcribe.js +0 -89
- package/dist/integrations/composite-ai/document-transcribe.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/chat.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/chat.post.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/chat.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/complete.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/complete.post.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/complete.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/generate.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/generate.post.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/generate.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/models.get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/models.get.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/models.get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/transcribe.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/transcribe.post.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/transcribe.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/transcript-cleanup.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/transcript-cleanup.post.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/transcript-cleanup.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/assets/[id]/[name].get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/assets/[id]/[name].get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/assets/[id]/[name].get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/assets/[id].post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/assets/[id].post.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/assets/[id].post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/settings.get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/settings.get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/settings.get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/settings.put.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/settings.put.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/settings.put.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/tags.get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/tags.get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/tags.get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/themes/[id].delete.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/themes/[id].delete.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/themes/[id].delete.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/themes/[id].get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/themes/[id].get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/themes/[id].get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/themes/index.get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/themes/index.get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/themes/index.get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/themes/index.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/themes/index.post.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/themes/index.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/users.get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/users.get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/users.get.js.map +0 -1
- package/dist/nuxt/runtime/server/plugins/collab.d.ts +0 -5
- package/dist/nuxt/runtime/server/plugins/collab.js +0 -10
- package/dist/nuxt/runtime/server/plugins/collab.js.map +0 -1
- package/dist/nuxt/runtime/server/routes/collab/[id].d.ts +0 -5
- package/dist/nuxt/runtime/server/routes/collab/[id].js +0 -35
- package/dist/nuxt/runtime/server/routes/collab/[id].js.map +0 -1
- package/dist/nuxt/runtime/server/utils/editor-route.d.ts +0 -11
- package/dist/nuxt/runtime/server/utils/editor-route.js.map +0 -1
- package/dist/presets/advanced.d.ts +0 -33
- package/dist/presets/advanced.js +0 -50
- package/dist/presets/advanced.js.map +0 -1
- package/dist/presets/simple.d.ts +0 -9
- package/dist/presets/simple.js +0 -12
- package/dist/presets/simple.js.map +0 -1
- package/dist/react/advanced-preset.d.ts +0 -11
- package/dist/react/advanced-preset.js +0 -31
- package/dist/react/advanced-preset.js.map +0 -1
- package/dist/vue/advanced-preset.d.ts +0 -13
- package/dist/vue/advanced-preset.js +0 -33
- package/dist/vue/advanced-preset.js.map +0 -1
- package/docs/extending.md +0 -538
- package/docs/integration.md +0 -632
- package/docs/llms.md +0 -383
- package/docs/usage.md +0 -1076
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/media/feature.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { FileHandler } from '@tiptap/extension-file-handler'\nimport
|
|
1
|
+
{"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/media/feature.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { FileHandler } from '@tiptap/extension-file-handler'\nimport type { CommandDef, EditorFeature } from '../../core'\nimport { ABILITY } from '../../core/schema'\nimport type { MessageKey } from '../../i18n'\nimport { fileImport } from './file-import'\nimport { type MediaNodeViews, mediaBlocks } from './node-views'\nimport { acceptFor, ImageNode, mediaSchemaExtensions, type MediaKind } from './schema'\nimport { Media, openAudioRecorder, pickFiles, uploadOf } from './upload'\n\n/** What a framework binding supplies to the media feature. */\nexport interface MediaUi<C> {\n /** The „Audio aufnehmen“ modal; mounted as an overlay, opened by `openAudioRecorder(editor)`. */\n recorderModal: C\n /** The audio, video and file block views of the framework. */\n nodeViews: MediaNodeViews\n}\n\nconst PICKERS: readonly { id: string, labelKey: MessageKey, icon: string, kind: MediaKind }[] = [\n { id: 'bild', labelKey: 'media.picker.image', icon: 'i-lucide-image', kind: 'image' },\n { id: 'audio', labelKey: 'media.picker.audio', icon: 'i-lucide-file-audio', kind: 'audio' },\n { id: 'video', labelKey: 'media.picker.video', icon: 'i-lucide-video', kind: 'video' },\n]\n\n/**\n * Image, audio, video and file blocks with their views, upload (`backend.assets`, filed under the surface's `documentId`),\n * drop/paste of files, the audio recorder, and the toolbar and `/` entries that open the file picker or\n * recorder. Files import through the pipeline: text and Markdown files are parsed into blocks, everything else is\n * uploaded; the `wrapImport` stage (identity by default) may wrap the blocks, which are then inserted as they are.\n * Import problems reach the user through the surface's notice seam (`ctx.notify`).\n * Needs a backend with the `assets` capability.\n */\nexport function mediaFeatureCore<C>(ui: MediaUi<C>): EditorFeature<C> {\n const { AudioBlock, VideoBlock, FileBlock } = mediaBlocks(ui.nodeViews)\n return {\n id: 'media',\n dimension: 'content',\n backend: ['assets'],\n schemaExtensions: mediaSchemaExtensions,\n extensions: (ctx) => {\n const { importFiles } = fileImport(ctx.notify, ctx.t)\n return [\n ImageNode,\n AudioBlock,\n VideoBlock,\n FileBlock,\n Media.configure({ upload: uploadOf(ctx), importFiles }),\n FileHandler.configure({\n consumePasteEvent: true,\n onDrop: (editor, files, pos) => importFiles(editor, files, pos),\n onPaste: (editor, files) => importFiles(editor, files),\n }),\n ]\n },\n commands: (ctx): CommandDef<C>[] => [\n ...PICKERS.map((picker, index): CommandDef<C> => ({\n id: picker.id,\n toolbar: { group: 'inserts', order: 20 + index * 10 },\n slash: {},\n icon: picker.icon,\n label: ctx.t(picker.labelKey),\n ability: ABILITY.media,\n // The caret at the click: the picker takes a while and the document may change meanwhile.\n run: editor => void pickAndImport(editor, acceptFor(picker.kind), editor.state.selection.from),\n })),\n { id: 'aufnahme', toolbar: { group: 'inserts', order: 50 }, slash: {}, icon: 'i-lucide-mic', label: ctx.t('media.slash.record'), ability: ABILITY.record, run: openAudioRecorder },\n ],\n overlays: [ui.recorderModal],\n }\n}\n\nasync function pickAndImport(editor: Editor, accept: string, pos?: number): Promise<void> {\n const files = await pickFiles(accept)\n if (files.length && !editor.isDestroyed) await editor.storage.media.importFiles(editor, files, pos)\n}\n"],"mappings":";;;;;;;AAkBA,MAAM,UAA0F;CAC9F;EAAE,IAAI;EAAQ,UAAU;EAAsB,MAAM;EAAkB,MAAM;CAAQ;CACpF;EAAE,IAAI;EAAS,UAAU;EAAsB,MAAM;EAAuB,MAAM;CAAQ;CAC1F;EAAE,IAAI;EAAS,UAAU;EAAsB,MAAM;EAAkB,MAAM;CAAQ;AACvF;;;;;;;;;AAUA,SAAgB,iBAAoB,IAAkC;CACpE,MAAM,EAAE,YAAY,YAAY,cAAc,YAAY,GAAG,SAAS;CACtE,OAAO;EACL,IAAI;EACJ,WAAW;EACX,SAAS,CAAC,QAAQ;EAClB,kBAAkB;EAClB,aAAa,QAAQ;GACnB,MAAM,EAAE,gBAAgB,WAAW,IAAI,QAAQ,IAAI,CAAC;GACpD,OAAO;IACL;IACA;IACA;IACA;IACA,MAAM,UAAU;KAAE,QAAQ,SAAS,GAAG;KAAG;IAAY,CAAC;IACtD,YAAY,UAAU;KACpB,mBAAmB;KACnB,SAAS,QAAQ,OAAO,QAAQ,YAAY,QAAQ,OAAO,GAAG;KAC9D,UAAU,QAAQ,UAAU,YAAY,QAAQ,KAAK;IACvD,CAAC;GACH;EACF;EACA,WAAW,QAAyB,CAClC,GAAG,QAAQ,KAAK,QAAQ,WAA0B;GAChD,IAAI,OAAO;GACX,SAAS;IAAE,OAAO;IAAW,OAAO,KAAK,QAAQ;GAAG;GACpD,OAAO,CAAC;GACR,MAAM,OAAO;GACb,OAAO,IAAI,EAAE,OAAO,QAAQ;GAC5B,SAAS,QAAQ;GAEjB,MAAK,WAAU,KAAK,cAAc,QAAQ,UAAU,OAAO,IAAI,GAAG,OAAO,MAAM,UAAU,IAAI;EAC/F,EAAE,GACF;GAAE,IAAI;GAAY,SAAS;IAAE,OAAO;IAAW,OAAO;GAAG;GAAG,OAAO,CAAC;GAAG,MAAM;GAAgB,OAAO,IAAI,EAAE,oBAAoB;GAAG,SAAS,QAAQ;GAAQ,KAAK;EAAkB,CACnL;EACA,UAAU,CAAC,GAAG,aAAa;CAC7B;AACF;AAEA,eAAe,cAAc,QAAgB,QAAgB,KAA6B;CACxF,MAAM,QAAQ,MAAM,UAAU,MAAM;CACpC,IAAI,MAAM,UAAU,CAAC,OAAO,aAAa,MAAM,OAAO,QAAQ,MAAM,YAAY,QAAQ,OAAO,GAAG;AACpG"}
|
|
@@ -19,8 +19,8 @@ async function blocksOf(editor, file) {
|
|
|
19
19
|
const blocks = structureBlocks(editor, parseBlocks(editor, await file.text()).blocks);
|
|
20
20
|
return blocks.length ? blocks : [{ type: "paragraph" }];
|
|
21
21
|
}
|
|
22
|
-
const { url } = await editor.storage.media.upload(file);
|
|
23
|
-
return [mediaNodeFor(file, url) ?? fileNodeFor(file, url)];
|
|
22
|
+
const { url, sha256 } = await editor.storage.media.upload(file);
|
|
23
|
+
return [mediaNodeFor(file, url, sha256) ?? fileNodeFor(file, url, sha256)];
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Imports files into the editor. Text and Markdown files are parsed through the editor's block pipeline into blocks;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-import.js","names":[],"sources":["../../../src/features/media/file-import.ts"],"sourcesContent":["import type { Editor, JSONContent } from '@tiptap/core'\nimport type { Translate } from '../../i18n'\nimport { captureRange, fetchErrorMessage, type Notifier, parseBlocks, releaseRanges, resolveRange, structureBlocks, wrapImportBlocks } from '../../core'\nimport { mediaMime } from './schema'\nimport { fileNodeFor, type ImportFilesFn, mediaNodeFor } from './upload'\n\n/** Text files are read into the document, bigger ones are refused. */\nexport const MAX_TEXT_BYTES = 2 * 1024 * 1024\n/** Browsers report `''` (or something else) as the type of `.md` files, so the name decides too. */\nconst TEXT_EXTENSION = /\\.(md|markdown|txt)$/i\n\n/** Whether `file` is imported as text (parsed Markdown) rather than uploaded. */\nexport function isTextFile(file: File): boolean {\n const mime = mediaMime(file.type)\n return mime === 'text/markdown' || mime === 'text/plain' || TEXT_EXTENSION.test(file.name)\n}\n\n/** Blocks for `file`: its text parsed through the editor's block pipeline (parse, normalize, segment, structure), or the uploaded media/file block. */\nasync function blocksOf(editor: Editor, file: File): Promise<JSONContent[]> {\n if (isTextFile(file)) {\n const blocks = structureBlocks(editor, parseBlocks(editor, await file.text()).blocks)\n return blocks.length ? blocks : [{ type: 'paragraph' }]\n }\n const { url } = await editor.storage.media.upload(file)\n return [mediaNodeFor(file, url) ?? fileNodeFor(file, url)]\n}\n\n/**\n * Imports files into the editor. Text and Markdown files are parsed through the editor's block pipeline into blocks;\n * all other files are uploaded through `editor.storage.media.upload` and appear as their media or file block. The\n * blocks go through the pipeline's `wrapImport` stage (identity unless an installed feature wraps imports) and are\n * inserted as they are. Problems (an oversized text file, a failed upload) are reported through `notify` and the file\n * is skipped. Framework-free: the Vue and React bindings pass their notifier and translator (`useFileImport()`), the feature passes `ctx.notify` and `ctx.t`.\n */\nexport function fileImport(notify: Notifier, t: Translate): { importFiles: ImportFilesFn } {\n // Sequential so files land in selection order; a failed file is reported and skipped. A drop position is anchored,\n // since uploads take a while and the document may change meanwhile; later files follow the first one, because\n // `insertContentAt` leaves the selection right after what it inserted. Resolves whether every file was imported.\n async function importFiles(editor: Editor, files: File[] | FileList, pos?: number): Promise<boolean> {\n let anchor = pos === undefined ? null : captureRange(editor, { from: pos, to: pos })\n let allImported = true\n try {\n for (const file of Array.from(files)) {\n if (isTextFile(file) && file.size > MAX_TEXT_BYTES) {\n notify({ title: t('media.import.tooLarge.title'), description: t('media.import.tooLarge.description', { name: file.name }), kind: 'error' })\n allImported = false\n continue\n }\n try {\n const blocks = wrapImportBlocks(editor, file, await blocksOf(editor, file))\n if (editor.isDestroyed) return false\n const at = (anchor && resolveRange(editor.state, anchor)?.from) ?? editor.state.selection.to\n editor.chain().focus().insertContentAt(at, blocks).run()\n if (anchor) releaseRanges(editor, [anchor])\n anchor = null\n }\n catch (error) {\n notify({ title: t('media.import.failed'), description: fetchErrorMessage(error), kind: 'error' })\n allImported = false\n }\n }\n }\n finally {\n if (anchor) releaseRanges(editor, [anchor])\n }\n return allImported\n }\n\n return { importFiles }\n}\n"],"mappings":";;;;;;;AAOA,MAAa,iBAAiB;;AAE9B,MAAM,iBAAiB;;AAGvB,SAAgB,WAAW,MAAqB;CAC9C,MAAM,OAAO,UAAU,KAAK,IAAI;CAChC,OAAO,SAAS,mBAAmB,SAAS,gBAAgB,eAAe,KAAK,KAAK,IAAI;AAC3F;;AAGA,eAAe,SAAS,QAAgB,MAAoC;CAC1E,IAAI,WAAW,IAAI,GAAG;EACpB,MAAM,SAAS,gBAAgB,QAAQ,YAAY,QAAQ,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM;EACpF,OAAO,OAAO,SAAS,SAAS,CAAC,EAAE,MAAM,YAAY,CAAC;CACxD;CACA,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"file-import.js","names":[],"sources":["../../../src/features/media/file-import.ts"],"sourcesContent":["import type { Editor, JSONContent } from '@tiptap/core'\nimport type { Translate } from '../../i18n'\nimport { captureRange, fetchErrorMessage, type Notifier, parseBlocks, releaseRanges, resolveRange, structureBlocks, wrapImportBlocks } from '../../core'\nimport { mediaMime } from './schema'\nimport { fileNodeFor, type ImportFilesFn, mediaNodeFor } from './upload'\n\n/** Text files are read into the document, bigger ones are refused. */\nexport const MAX_TEXT_BYTES = 2 * 1024 * 1024\n/** Browsers report `''` (or something else) as the type of `.md` files, so the name decides too. */\nconst TEXT_EXTENSION = /\\.(md|markdown|txt)$/i\n\n/** Whether `file` is imported as text (parsed Markdown) rather than uploaded. */\nexport function isTextFile(file: File): boolean {\n const mime = mediaMime(file.type)\n return mime === 'text/markdown' || mime === 'text/plain' || TEXT_EXTENSION.test(file.name)\n}\n\n/** Blocks for `file`: its text parsed through the editor's block pipeline (parse, normalize, segment, structure), or the uploaded media/file block. */\nasync function blocksOf(editor: Editor, file: File): Promise<JSONContent[]> {\n if (isTextFile(file)) {\n const blocks = structureBlocks(editor, parseBlocks(editor, await file.text()).blocks)\n return blocks.length ? blocks : [{ type: 'paragraph' }]\n }\n const { url, sha256 } = await editor.storage.media.upload(file)\n return [mediaNodeFor(file, url, sha256) ?? fileNodeFor(file, url, sha256)]\n}\n\n/**\n * Imports files into the editor. Text and Markdown files are parsed through the editor's block pipeline into blocks;\n * all other files are uploaded through `editor.storage.media.upload` and appear as their media or file block. The\n * blocks go through the pipeline's `wrapImport` stage (identity unless an installed feature wraps imports) and are\n * inserted as they are. Problems (an oversized text file, a failed upload) are reported through `notify` and the file\n * is skipped. Framework-free: the Vue and React bindings pass their notifier and translator (`useFileImport()`), the feature passes `ctx.notify` and `ctx.t`.\n */\nexport function fileImport(notify: Notifier, t: Translate): { importFiles: ImportFilesFn } {\n // Sequential so files land in selection order; a failed file is reported and skipped. A drop position is anchored,\n // since uploads take a while and the document may change meanwhile; later files follow the first one, because\n // `insertContentAt` leaves the selection right after what it inserted. Resolves whether every file was imported.\n async function importFiles(editor: Editor, files: File[] | FileList, pos?: number): Promise<boolean> {\n let anchor = pos === undefined ? null : captureRange(editor, { from: pos, to: pos })\n let allImported = true\n try {\n for (const file of Array.from(files)) {\n if (isTextFile(file) && file.size > MAX_TEXT_BYTES) {\n notify({ title: t('media.import.tooLarge.title'), description: t('media.import.tooLarge.description', { name: file.name }), kind: 'error' })\n allImported = false\n continue\n }\n try {\n const blocks = wrapImportBlocks(editor, file, await blocksOf(editor, file))\n if (editor.isDestroyed) return false\n const at = (anchor && resolveRange(editor.state, anchor)?.from) ?? editor.state.selection.to\n editor.chain().focus().insertContentAt(at, blocks).run()\n if (anchor) releaseRanges(editor, [anchor])\n anchor = null\n }\n catch (error) {\n notify({ title: t('media.import.failed'), description: fetchErrorMessage(error), kind: 'error' })\n allImported = false\n }\n }\n }\n finally {\n if (anchor) releaseRanges(editor, [anchor])\n }\n return allImported\n }\n\n return { importFiles }\n}\n"],"mappings":";;;;;;;AAOA,MAAa,iBAAiB;;AAE9B,MAAM,iBAAiB;;AAGvB,SAAgB,WAAW,MAAqB;CAC9C,MAAM,OAAO,UAAU,KAAK,IAAI;CAChC,OAAO,SAAS,mBAAmB,SAAS,gBAAgB,eAAe,KAAK,KAAK,IAAI;AAC3F;;AAGA,eAAe,SAAS,QAAgB,MAAoC;CAC1E,IAAI,WAAW,IAAI,GAAG;EACpB,MAAM,SAAS,gBAAgB,QAAQ,YAAY,QAAQ,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM;EACpF,OAAO,OAAO,SAAS,SAAS,CAAC,EAAE,MAAM,YAAY,CAAC;CACxD;CACA,MAAM,EAAE,KAAK,WAAW,MAAM,OAAO,QAAQ,MAAM,OAAO,IAAI;CAC9D,OAAO,CAAC,aAAa,MAAM,KAAK,MAAM,KAAK,YAAY,MAAM,KAAK,MAAM,CAAC;AAC3E;;;;;;;;AASA,SAAgB,WAAW,QAAkB,GAA8C;CAIzF,eAAe,YAAY,QAAgB,OAA0B,KAAgC;EACnG,IAAI,SAAS,QAAQ,KAAA,IAAY,OAAO,aAAa,QAAQ;GAAE,MAAM;GAAK,IAAI;EAAI,CAAC;EACnF,IAAI,cAAc;EAClB,IAAI;GACF,KAAK,MAAM,QAAQ,MAAM,KAAK,KAAK,GAAG;IACpC,IAAI,WAAW,IAAI,KAAK,KAAK,OAAA,SAAuB;KAClD,OAAO;MAAE,OAAO,EAAE,6BAA6B;MAAG,aAAa,EAAE,qCAAqC,EAAE,MAAM,KAAK,KAAK,CAAC;MAAG,MAAM;KAAQ,CAAC;KAC3I,cAAc;KACd;IACF;IACA,IAAI;KACF,MAAM,SAAS,iBAAiB,QAAQ,MAAM,MAAM,SAAS,QAAQ,IAAI,CAAC;KAC1E,IAAI,OAAO,aAAa,OAAO;KAC/B,MAAM,MAAM,UAAU,aAAa,OAAO,OAAO,MAAM,CAAC,EAAE,SAAS,OAAO,MAAM,UAAU;KAC1F,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,gBAAgB,IAAI,MAAM,CAAC,CAAC,IAAI;KACvD,IAAI,QAAQ,cAAc,QAAQ,CAAC,MAAM,CAAC;KAC1C,SAAS;IACX,SACO,OAAO;KACZ,OAAO;MAAE,OAAO,EAAE,qBAAqB;MAAG,aAAa,kBAAkB,KAAK;MAAG,MAAM;KAAQ,CAAC;KAChG,cAAc;IAChB;GACF;EACF,UACQ;GACN,IAAI,QAAQ,cAAc,QAAQ,CAAC,MAAM,CAAC;EAC5C;EACA,OAAO;CACT;CAEA,OAAO,EAAE,YAAY;AACvB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MAX_MEDIA_BYTES, MEDIA_TYPES, MediaKind, acceptFor, mediaKindOf, mediaMime } from "./schema.js";
|
|
2
2
|
import { AudioRecorder, AudioRecorderState, AudioRecorderStatus, createAudioRecorder } from "./audio-recorder.js";
|
|
3
|
-
import { AUDIO_RECORDER_OPEN, ImportFilesFn, Media, fileNodeFor, formatBytes, mediaNodeFor, openAudioRecorder, pickFiles,
|
|
3
|
+
import { AUDIO_RECORDER_OPEN, ImportFilesFn, Media, fileNodeFor, formatBytes, mediaNodeFor, openAudioRecorder, pickFiles, uploadOf } from "./upload.js";
|
|
4
4
|
import { MAX_TEXT_BYTES, fileImport, isTextFile } from "./file-import.js";
|
|
5
5
|
import { MediaNodeViews, mediaBlocks, playerOptions } from "./node-views.js";
|
|
6
6
|
import { MediaUi, mediaFeatureCore } from "./feature.js";
|
|
7
|
-
export { AUDIO_RECORDER_OPEN, type AudioRecorder, type AudioRecorderState, type AudioRecorderStatus, type ImportFilesFn, MAX_MEDIA_BYTES, MAX_TEXT_BYTES, MEDIA_TYPES, Media, type MediaKind, type MediaNodeViews, type MediaUi, acceptFor, createAudioRecorder, fileImport, fileNodeFor, formatBytes, isTextFile, mediaBlocks, mediaFeatureCore, mediaKindOf, mediaMime, mediaNodeFor, openAudioRecorder, pickFiles, playerOptions,
|
|
7
|
+
export { AUDIO_RECORDER_OPEN, type AudioRecorder, type AudioRecorderState, type AudioRecorderStatus, type ImportFilesFn, MAX_MEDIA_BYTES, MAX_TEXT_BYTES, MEDIA_TYPES, Media, type MediaKind, type MediaNodeViews, type MediaUi, acceptFor, createAudioRecorder, fileImport, fileNodeFor, formatBytes, isTextFile, mediaBlocks, mediaFeatureCore, mediaKindOf, mediaMime, mediaNodeFor, openAudioRecorder, pickFiles, playerOptions, uploadOf };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MAX_MEDIA_BYTES, MEDIA_TYPES, acceptFor, mediaKindOf, mediaMime } from "./schema.js";
|
|
2
2
|
import { createAudioRecorder } from "./audio-recorder.js";
|
|
3
|
-
import { AUDIO_RECORDER_OPEN, Media, fileNodeFor, formatBytes, mediaNodeFor, openAudioRecorder, pickFiles,
|
|
3
|
+
import { AUDIO_RECORDER_OPEN, Media, fileNodeFor, formatBytes, mediaNodeFor, openAudioRecorder, pickFiles, uploadOf } from "./upload.js";
|
|
4
4
|
import { MAX_TEXT_BYTES, fileImport, isTextFile } from "./file-import.js";
|
|
5
5
|
import { mediaBlocks, playerOptions } from "./node-views.js";
|
|
6
6
|
import { mediaFeatureCore } from "./feature.js";
|
|
7
|
-
export { AUDIO_RECORDER_OPEN, MAX_MEDIA_BYTES, MAX_TEXT_BYTES, MEDIA_TYPES, Media, acceptFor, createAudioRecorder, fileImport, fileNodeFor, formatBytes, isTextFile, mediaBlocks, mediaFeatureCore, mediaKindOf, mediaMime, mediaNodeFor, openAudioRecorder, pickFiles, playerOptions,
|
|
7
|
+
export { AUDIO_RECORDER_OPEN, MAX_MEDIA_BYTES, MAX_TEXT_BYTES, MEDIA_TYPES, Media, acceptFor, createAudioRecorder, fileImport, fileNodeFor, formatBytes, isTextFile, mediaBlocks, mediaFeatureCore, mediaKindOf, mediaMime, mediaNodeFor, openAudioRecorder, pickFiles, playerOptions, uploadOf };
|
|
@@ -4,6 +4,7 @@ import { Icon } from "../../../react/Icon.js";
|
|
|
4
4
|
import { Button } from "../../../react/ui/button.js";
|
|
5
5
|
import { formatBytes } from "../upload.js";
|
|
6
6
|
import { TitleField } from "./TitleField.js";
|
|
7
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
9
|
import { NodeViewWrapper } from "@tiptap/react";
|
|
9
10
|
//#region src/features/media/react/FileView.tsx
|
|
@@ -11,7 +12,23 @@ import { NodeViewWrapper } from "@tiptap/react";
|
|
|
11
12
|
function FileView({ node, selected, editor, updateAttributes }) {
|
|
12
13
|
const { t } = useEditorI18n();
|
|
13
14
|
const isPdf = node.attrs.mimeType === "application/pdf";
|
|
15
|
+
const [reveal, setReveal] = useState(null);
|
|
16
|
+
const figure = useRef(null);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const el = figure.current;
|
|
19
|
+
if (!el) return;
|
|
20
|
+
const onReveal = (event) => {
|
|
21
|
+
const detail = event.detail;
|
|
22
|
+
if (typeof detail === "number" && detail > 0) setReveal((prev) => ({
|
|
23
|
+
page: detail,
|
|
24
|
+
n: (prev?.n ?? 0) + 1
|
|
25
|
+
}));
|
|
26
|
+
};
|
|
27
|
+
el.addEventListener("sk-reveal-page", onReveal);
|
|
28
|
+
return () => el.removeEventListener("sk-reveal-page", onReveal);
|
|
29
|
+
}, []);
|
|
14
30
|
return /* @__PURE__ */ jsxs(NodeViewWrapper, {
|
|
31
|
+
ref: figure,
|
|
15
32
|
as: "figure",
|
|
16
33
|
className: cn("media-block flex flex-col gap-1", selected && "is-selected"),
|
|
17
34
|
"data-id": node.attrs.id || void 0,
|
|
@@ -48,11 +65,11 @@ function FileView({ node, selected, editor, updateAttributes }) {
|
|
|
48
65
|
})
|
|
49
66
|
]
|
|
50
67
|
}), isPdf && node.attrs.src ? /* @__PURE__ */ jsx("object", {
|
|
51
|
-
data: node.attrs.src,
|
|
68
|
+
data: reveal ? `${node.attrs.src}#page=${reveal.page}` : node.attrs.src,
|
|
52
69
|
type: "application/pdf",
|
|
53
70
|
contentEditable: false,
|
|
54
71
|
className: "mt-2 h-[480px] w-full rounded-md border border-border"
|
|
55
|
-
}) : null]
|
|
72
|
+
}, reveal?.n ?? 0) : null]
|
|
56
73
|
});
|
|
57
74
|
}
|
|
58
75
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileView.js","names":[],"sources":["../../../../src/features/media/react/FileView.tsx"],"sourcesContent":["import { NodeViewWrapper, type NodeViewProps } from '@tiptap/react'\nimport { Icon } from '../../../react/Icon'\nimport { Button } from '../../../react/ui/button'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { formatBytes } from '../upload'\nimport { TitleField } from './TitleField'\n\n/** The file block: icon, name field, size and „Öffnen“ link; PDFs are embedded below. */\nexport function FileView({ node, selected, editor, updateAttributes }: NodeViewProps) {\n const { t } = useEditorI18n()\n const isPdf = node.attrs.mimeType === 'application/pdf'\n return (\n <NodeViewWrapper\n as=\"figure\"\n className={cn('media-block flex flex-col gap-1', selected && 'is-selected')}\n data-id={node.attrs.id || undefined}\n >\n <div className=\"flex items-center gap-2\" contentEditable={false}>\n <Icon name={isPdf ? 'i-lucide-file-text' : 'i-lucide-file'} className=\"size-5 text-muted-foreground\" />\n <TitleField\n value={node.attrs.title}\n placeholder={t('media.view.fileName')}\n label={t('media.view.fileName')}\n readOnly={!editor.isEditable}\n className=\"min-w-0 flex-1\"\n onChange={title => updateAttributes({ title })}\n />\n {node.attrs.size ? <span className=\"shrink-0 text-xs text-muted-foreground/60\">{formatBytes(node.attrs.size)}</span> : null}\n <Button asChild size=\"xs\" variant=\"secondary\">\n <a href={node.attrs.src} target=\"_blank\" rel=\"noopener noreferrer\">\n <Icon name=\"i-lucide-external-link\" />\n {t('media.view.open')}\n </a>\n </Button>\n </div>\n {isPdf && node.attrs.src\n ? <object data={node.attrs.src} type=\"application/pdf\" contentEditable={false} className=\"mt-2 h-[480px] w-full rounded-md border border-border\" />\n : null}\n </NodeViewWrapper>\n )\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"FileView.js","names":[],"sources":["../../../../src/features/media/react/FileView.tsx"],"sourcesContent":["import { NodeViewWrapper, type NodeViewProps } from '@tiptap/react'\nimport { useEffect, useRef, useState } from 'react'\nimport { Icon } from '../../../react/Icon'\nimport { Button } from '../../../react/ui/button'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { formatBytes } from '../upload'\nimport { TitleField } from './TitleField'\n\n/** The file block: icon, name field, size and „Öffnen“ link; PDFs are embedded below. */\nexport function FileView({ node, selected, editor, updateAttributes }: NodeViewProps) {\n const { t } = useEditorI18n()\n const isPdf = node.attrs.mimeType === 'application/pdf'\n // Page the PDF viewer was asked to open at (`revealSource`, locator „S. 3“). `n` counts the requests: the viewer is\n // remounted on every one, so asking for the same page again jumps back after the user scrolled away.\n const [reveal, setReveal] = useState<{ page: number, n: number } | null>(null)\n const figure = useRef<HTMLElement>(null)\n useEffect(() => {\n const el = figure.current\n if (!el) return\n const onReveal = (event: Event) => {\n const detail = (event as CustomEvent<unknown>).detail\n if (typeof detail === 'number' && detail > 0) setReveal(prev => ({ page: detail, n: (prev?.n ?? 0) + 1 }))\n }\n // Event name of `revealSource` in features/composites/reveal-source.ts (media must not import composites).\n el.addEventListener('sk-reveal-page', onReveal)\n return () => el.removeEventListener('sk-reveal-page', onReveal)\n }, [])\n\n return (\n <NodeViewWrapper\n ref={figure}\n as=\"figure\"\n className={cn('media-block flex flex-col gap-1', selected && 'is-selected')}\n data-id={node.attrs.id || undefined}\n >\n <div className=\"flex items-center gap-2\" contentEditable={false}>\n <Icon name={isPdf ? 'i-lucide-file-text' : 'i-lucide-file'} className=\"size-5 text-muted-foreground\" />\n <TitleField\n value={node.attrs.title}\n placeholder={t('media.view.fileName')}\n label={t('media.view.fileName')}\n readOnly={!editor.isEditable}\n className=\"min-w-0 flex-1\"\n onChange={title => updateAttributes({ title })}\n />\n {node.attrs.size ? <span className=\"shrink-0 text-xs text-muted-foreground/60\">{formatBytes(node.attrs.size)}</span> : null}\n <Button asChild size=\"xs\" variant=\"secondary\">\n <a href={node.attrs.src} target=\"_blank\" rel=\"noopener noreferrer\">\n <Icon name=\"i-lucide-external-link\" />\n {t('media.view.open')}\n </a>\n </Button>\n </div>\n {isPdf && node.attrs.src\n ? <object key={reveal?.n ?? 0} data={reveal ? `${node.attrs.src}#page=${reveal.page}` : node.attrs.src} type=\"application/pdf\" contentEditable={false} className=\"mt-2 h-[480px] w-full rounded-md border border-border\" />\n : null}\n </NodeViewWrapper>\n )\n}\n"],"mappings":";;;;;;;;;;;AAUA,SAAgB,SAAS,EAAE,MAAM,UAAU,QAAQ,oBAAmC;CACpF,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,QAAQ,KAAK,MAAM,aAAa;CAGtC,MAAM,CAAC,QAAQ,aAAa,SAA6C,IAAI;CAC7E,MAAM,SAAS,OAAoB,IAAI;CACvC,gBAAgB;EACd,MAAM,KAAK,OAAO;EAClB,IAAI,CAAC,IAAI;EACT,MAAM,YAAY,UAAiB;GACjC,MAAM,SAAU,MAA+B;GAC/C,IAAI,OAAO,WAAW,YAAY,SAAS,GAAG,WAAU,UAAS;IAAE,MAAM;IAAQ,IAAI,MAAM,KAAK,KAAK;GAAE,EAAE;EAC3G;EAEA,GAAG,iBAAiB,kBAAkB,QAAQ;EAC9C,aAAa,GAAG,oBAAoB,kBAAkB,QAAQ;CAChE,GAAG,CAAC,CAAC;CAEL,OACE,qBAAC,iBAAD;EACE,KAAK;EACL,IAAG;EACH,WAAW,GAAG,mCAAmC,YAAY,aAAa;EAC1E,WAAS,KAAK,MAAM,MAAM,KAAA;EAJ5B,UAAA,CAME,qBAAC,OAAD;GAAK,WAAU;GAA0B,iBAAiB;GAA1D,UAAA;IACE,oBAAC,MAAD;KAAM,MAAM,QAAQ,uBAAuB;KAAiB,WAAU;IAAgC,CAAA;IACtG,oBAAC,YAAD;KACE,OAAO,KAAK,MAAM;KAClB,aAAa,EAAE,qBAAqB;KACpC,OAAO,EAAE,qBAAqB;KAC9B,UAAU,CAAC,OAAO;KAClB,WAAU;KACV,WAAU,UAAS,iBAAiB,EAAE,MAAM,CAAC;IAC9C,CAAA;IACA,KAAK,MAAM,OAAO,oBAAC,QAAD;KAAM,WAAU;KAA6C,UAAA,YAAY,KAAK,MAAM,IAAI;IAAQ,CAAA,IAAI;IACvH,oBAAC,QAAD;KAAQ,SAAA;KAAQ,MAAK;KAAK,SAAQ;KAChC,UAAA,qBAAC,KAAD;MAAG,MAAM,KAAK,MAAM;MAAK,QAAO;MAAS,KAAI;MAA7C,UAAA,CACE,oBAAC,MAAD,EAAM,MAAK,yBAA0B,CAAA,GACpC,EAAE,iBAAiB,CACnB;;IACG,CAAA;GACL;EACJ,CAAA,GAAA,SAAS,KAAK,MAAM,MACjB,oBAAC,UAAD;GAA6B,MAAM,SAAS,GAAG,KAAK,MAAM,IAAI,QAAQ,OAAO,SAAS,KAAK,MAAM;GAAK,MAAK;GAAkB,iBAAiB;GAAO,WAAU;EAAyD,GAA3M,QAAQ,KAAK,CAA8L,IACxN,IACW;;AAErB"}
|
|
@@ -3,6 +3,12 @@ import { AnyExtension, Node } from "@tiptap/core";
|
|
|
3
3
|
//#region src/features/media/schema.d.ts
|
|
4
4
|
/** The media nodes (image, audio, video, file) of the document schema. */
|
|
5
5
|
export declare const mediaSchemaExtensions: () => AnyExtension[];
|
|
6
|
+
/**
|
|
7
|
+
* The image node with a `hash` attribute. Markdown: ``, followed by ` <!-- {"hash":"…"} -->` when the
|
|
8
|
+
* block has a hash; only that form is claimed by `imageBlockTokenizer` (a block-level token that still yields an `image`
|
|
9
|
+
* token), a plain image keeps the stock inline parse.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ImageNode: Node<import("@tiptap/extension-image").ImageOptions, any>;
|
|
6
12
|
export type MediaKind = 'image' | 'audio' | 'video' | 'file';
|
|
7
13
|
/** Uploadable MIME types → file extension on disk and media kind. `<uuid>.<ext>` asset names derive from this. */
|
|
8
14
|
export declare const MEDIA_TYPES: Readonly<Record<string, {
|
|
@@ -22,6 +28,7 @@ export interface MediaAttrs {
|
|
|
22
28
|
src: string;
|
|
23
29
|
title?: string;
|
|
24
30
|
mimeType?: string;
|
|
31
|
+
hash?: string | null;
|
|
25
32
|
}
|
|
26
33
|
declare module '@tiptap/core' {
|
|
27
34
|
interface Commands<ReturnType> {
|
|
@@ -51,6 +58,7 @@ export interface FileAttrs {
|
|
|
51
58
|
title?: string;
|
|
52
59
|
mimeType?: string;
|
|
53
60
|
size?: number;
|
|
61
|
+
hash?: string | null;
|
|
54
62
|
}
|
|
55
63
|
/** A generic uploaded file (PDF, office document, …) that has no player: shown as a card with an open button. */
|
|
56
64
|
export declare const FileNode: Node<any, any>;
|
|
@@ -3,13 +3,97 @@ import { ASSET_DOCUMENT_ID, ASSET_NAME, SCRATCH_DOCUMENT_ID, assetRefOf, assetUr
|
|
|
3
3
|
import { Node, mergeAttributes } from "@tiptap/core";
|
|
4
4
|
import { Image } from "@tiptap/extension-image";
|
|
5
5
|
//#region src/features/media/schema.ts
|
|
6
|
+
/** SHA-256 (hex) of the uploaded bytes, kept on media blocks so derived content can tell when its source changed; null when unknown (memory backend, older documents). */
|
|
7
|
+
const hashAttribute = {
|
|
8
|
+
default: null,
|
|
9
|
+
parseHTML: (el) => el.getAttribute("data-hash"),
|
|
10
|
+
renderHTML: (attrs) => attrs.hash ? { "data-hash": attrs.hash } : {}
|
|
11
|
+
};
|
|
6
12
|
/** The media nodes (image, audio, video, file) of the document schema. */
|
|
7
13
|
const mediaSchemaExtensions = () => [
|
|
8
|
-
|
|
14
|
+
ImageNode,
|
|
9
15
|
AudioNode,
|
|
10
16
|
VideoNode,
|
|
11
17
|
FileNode
|
|
12
18
|
];
|
|
19
|
+
/**
|
|
20
|
+
* The image node with a `hash` attribute. Markdown: ``, followed by ` <!-- {"hash":"…"} -->` when the
|
|
21
|
+
* block has a hash; only that form is claimed by `imageBlockTokenizer` (a block-level token that still yields an `image`
|
|
22
|
+
* token), a plain image keeps the stock inline parse.
|
|
23
|
+
*/
|
|
24
|
+
const ImageNode = Image.extend({
|
|
25
|
+
addAttributes() {
|
|
26
|
+
return {
|
|
27
|
+
...this.parent?.(),
|
|
28
|
+
hash: hashAttribute
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
markdownTokenizer: {
|
|
32
|
+
name: "imageBlock",
|
|
33
|
+
level: "block",
|
|
34
|
+
start: (src) => src.search(new RegExp(IMAGE_BLOCK_PATTERN, "m")),
|
|
35
|
+
tokenize: (src) => {
|
|
36
|
+
const match = new RegExp(IMAGE_BLOCK_PATTERN).exec(src);
|
|
37
|
+
if (!match) return void 0;
|
|
38
|
+
return {
|
|
39
|
+
type: "image",
|
|
40
|
+
raw: match[0],
|
|
41
|
+
text: match[1] ?? "",
|
|
42
|
+
href: match[2] ?? "",
|
|
43
|
+
title: match[3],
|
|
44
|
+
attrs: parseAttrComment(match[4])
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
parseMarkdown: (token, helpers) => helpers.createNode("image", {
|
|
49
|
+
src: token.href,
|
|
50
|
+
title: token.title,
|
|
51
|
+
alt: token.text,
|
|
52
|
+
hash: typeof token.attrs?.hash === "string" ? token.attrs.hash : null
|
|
53
|
+
}),
|
|
54
|
+
renderMarkdown: (node) => {
|
|
55
|
+
const { src = "", alt = "", title = "" } = node.attrs ?? {};
|
|
56
|
+
return `${attrComment({ hash: node.attrs?.hash })}`;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
const ATTR_COMMENT_PATTERN = "(?: <!-- (\\{.*?\\}) -->)?[ \\t]*(?:\\n|$)";
|
|
60
|
+
const IMAGE_BLOCK_PATTERN = `^!\\[([^\\]\\n]*)\\]\\(([^)\\s]*)(?: "([^"\\n]*)")?\\) <!-- (\\{.*?\\}) -->[ \\t]*(?:\\n|$)`;
|
|
61
|
+
/** ` <!-- {"hash":"…"} -->` for the attributes that are set (empty string when none); `>` is escaped so the JSON cannot end the comment. */
|
|
62
|
+
function attrComment(attrs) {
|
|
63
|
+
const set = Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== null && value !== void 0 && value !== "" && value !== 0));
|
|
64
|
+
return Object.keys(set).length ? ` <!-- ${JSON.stringify(set).replace(/>/g, "\\u003e")} -->` : "";
|
|
65
|
+
}
|
|
66
|
+
/** The attributes of an `attrComment`; empty for a missing or malformed one. */
|
|
67
|
+
function parseAttrComment(json) {
|
|
68
|
+
if (!json) return {};
|
|
69
|
+
try {
|
|
70
|
+
const value = JSON.parse(json);
|
|
71
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
72
|
+
} catch {
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Block tokenizer for `[<label>: title](src)` lines of one media node; `labels` are the accepted labels (regex alternatives). */
|
|
77
|
+
function mediaTokenizer(name, labels) {
|
|
78
|
+
const pattern = `^\\[(?:${labels})(?:: ((?:\\\\.|[^\\]\\\\\\n])*))?\\]\\(([^)\\s]*)\\)${ATTR_COMMENT_PATTERN}`;
|
|
79
|
+
return {
|
|
80
|
+
name,
|
|
81
|
+
level: "block",
|
|
82
|
+
start: (src) => src.search(new RegExp(pattern, "m")),
|
|
83
|
+
tokenize: (src) => {
|
|
84
|
+
const match = new RegExp(pattern).exec(src);
|
|
85
|
+
if (!match) return void 0;
|
|
86
|
+
return {
|
|
87
|
+
type: name,
|
|
88
|
+
raw: match[0],
|
|
89
|
+
title: (match[1] ?? "").replace(/\\(.)/g, "$1"),
|
|
90
|
+
href: match[2] ?? "",
|
|
91
|
+
attrs: parseAttrComment(match[3])
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const stringAttr = (value) => typeof value === "string" ? value : "";
|
|
13
97
|
/** Uploadable MIME types → file extension on disk and media kind. `<uuid>.<ext>` asset names derive from this. */
|
|
14
98
|
const MEDIA_TYPES = {
|
|
15
99
|
"image/png": {
|
|
@@ -97,11 +181,25 @@ const mediaAttributes = {
|
|
|
97
181
|
default: "",
|
|
98
182
|
parseHTML: (el) => el.getAttribute("data-mime") ?? "",
|
|
99
183
|
renderHTML: (attrs) => ({ "data-mime": attrs.mimeType })
|
|
100
|
-
}
|
|
184
|
+
},
|
|
185
|
+
hash: hashAttribute
|
|
101
186
|
};
|
|
102
|
-
/** Markdown export (AI context, `getMarkdown()
|
|
103
|
-
function mediaMarkdown(label,
|
|
104
|
-
|
|
187
|
+
/** Markdown export (AI context, `getMarkdown()`, source mode): a link labelled with the kind and, when there is one, the title; `extra` and the hash go into the trailing attribute comment. */
|
|
188
|
+
function mediaMarkdown(label, attrs, extra) {
|
|
189
|
+
const title = attrs?.title;
|
|
190
|
+
return `[${title ? `${label}: ${String(title).replace(/[[\]\\]/g, "\\$&")}` : label}](${String(attrs?.src ?? "")})${attrComment({
|
|
191
|
+
...extra,
|
|
192
|
+
hash: attrs?.hash
|
|
193
|
+
})}`;
|
|
194
|
+
}
|
|
195
|
+
/** Node attributes of an audio/video Markdown token. */
|
|
196
|
+
function mediaAttrsOfToken(token) {
|
|
197
|
+
return {
|
|
198
|
+
src: token.href,
|
|
199
|
+
title: token.title,
|
|
200
|
+
mimeType: stringAttr(token.attrs?.mimeType),
|
|
201
|
+
hash: stringAttr(token.attrs?.hash) || null
|
|
202
|
+
};
|
|
105
203
|
}
|
|
106
204
|
const AudioNode = Node.create({
|
|
107
205
|
name: "audio",
|
|
@@ -115,7 +213,9 @@ const AudioNode = Node.create({
|
|
|
115
213
|
controls: "true",
|
|
116
214
|
preload: "metadata"
|
|
117
215
|
})],
|
|
118
|
-
|
|
216
|
+
markdownTokenizer: mediaTokenizer("audio", "Audio"),
|
|
217
|
+
parseMarkdown: (token, helpers) => helpers.createNode("audio", mediaAttrsOfToken(token)),
|
|
218
|
+
renderMarkdown: (node) => mediaMarkdown("Audio", node.attrs, { mimeType: node.attrs?.mimeType }),
|
|
119
219
|
addCommands() {
|
|
120
220
|
return { insertAudio: (attrs) => ({ commands }) => commands.insertContent({
|
|
121
221
|
type: this.name,
|
|
@@ -135,7 +235,9 @@ const VideoNode = Node.create({
|
|
|
135
235
|
controls: "true",
|
|
136
236
|
preload: "metadata"
|
|
137
237
|
})],
|
|
138
|
-
|
|
238
|
+
markdownTokenizer: mediaTokenizer("video", "Video"),
|
|
239
|
+
parseMarkdown: (token, helpers) => helpers.createNode("video", mediaAttrsOfToken(token)),
|
|
240
|
+
renderMarkdown: (node) => mediaMarkdown("Video", node.attrs, { mimeType: node.attrs?.mimeType }),
|
|
139
241
|
addCommands() {
|
|
140
242
|
return { insertVideo: (attrs) => ({ commands }) => commands.insertContent({
|
|
141
243
|
type: this.name,
|
|
@@ -166,7 +268,8 @@ const FileNode = Node.create({
|
|
|
166
268
|
size: {
|
|
167
269
|
default: 0,
|
|
168
270
|
parseHTML: (el) => Number(el.getAttribute("data-size")) || 0
|
|
169
|
-
}
|
|
271
|
+
},
|
|
272
|
+
hash: hashAttribute
|
|
170
273
|
}),
|
|
171
274
|
parseHTML: () => [{ tag: "a[data-type=\"file\"]" }],
|
|
172
275
|
renderHTML: ({ node }) => [
|
|
@@ -176,11 +279,23 @@ const FileNode = Node.create({
|
|
|
176
279
|
"href": node.attrs.src,
|
|
177
280
|
"download": node.attrs.title,
|
|
178
281
|
"data-mime": node.attrs.mimeType,
|
|
179
|
-
"data-size": String(node.attrs.size)
|
|
282
|
+
"data-size": String(node.attrs.size),
|
|
283
|
+
...node.attrs.hash ? { "data-hash": node.attrs.hash } : {}
|
|
180
284
|
},
|
|
181
285
|
node.attrs.title || "Datei"
|
|
182
286
|
],
|
|
183
|
-
|
|
287
|
+
markdownTokenizer: mediaTokenizer("file", "Datei|File"),
|
|
288
|
+
parseMarkdown: (token, helpers) => helpers.createNode("file", {
|
|
289
|
+
src: token.href,
|
|
290
|
+
title: token.title,
|
|
291
|
+
mimeType: stringAttr(token.attrs?.mimeType),
|
|
292
|
+
size: typeof token.attrs?.size === "number" ? token.attrs.size : 0,
|
|
293
|
+
hash: stringAttr(token.attrs?.hash) || null
|
|
294
|
+
}),
|
|
295
|
+
renderMarkdown: (node) => mediaMarkdown("Datei", node.attrs, {
|
|
296
|
+
mimeType: node.attrs?.mimeType,
|
|
297
|
+
size: node.attrs?.size
|
|
298
|
+
}),
|
|
184
299
|
addCommands() {
|
|
185
300
|
return { insertFile: (attrs) => ({ commands }) => commands.insertContent({
|
|
186
301
|
type: this.name,
|
|
@@ -189,6 +304,6 @@ const FileNode = Node.create({
|
|
|
189
304
|
}
|
|
190
305
|
});
|
|
191
306
|
//#endregion
|
|
192
|
-
export { ASSET_DOCUMENT_ID, ASSET_NAME, AudioNode, FileNode, MAX_MEDIA_BYTES, MEDIA_TYPES, SCRATCH_DOCUMENT_ID, VideoNode, acceptFor, assetRefOf, assetUrl, mediaKindOf, mediaMime, mediaSchemaExtensions };
|
|
307
|
+
export { ASSET_DOCUMENT_ID, ASSET_NAME, AudioNode, FileNode, ImageNode, MAX_MEDIA_BYTES, MEDIA_TYPES, SCRATCH_DOCUMENT_ID, VideoNode, acceptFor, assetRefOf, assetUrl, mediaKindOf, mediaMime, mediaSchemaExtensions };
|
|
193
308
|
|
|
194
309
|
//# sourceMappingURL=schema.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","names":[],"sources":["../../../src/features/media/schema.ts"],"sourcesContent":["import { type Attributes, type AnyExtension, mergeAttributes, Node } from '@tiptap/core'\nimport { Image } from '@tiptap/extension-image'\nimport { TEXT_ABILITIES } from '../../core/schema'\n\nexport * from './assets'\n\n// Vue-free and server-importable (`@ecsplico/editor/media/schema`): the upload API validates against these types, the\n// schema has the nodes. The client adds the node views in node-views.ts.\n\n/** The media nodes (image, audio, video, file) of the document schema. */\nexport const mediaSchemaExtensions = (): AnyExtension[] => [Image, AudioNode, VideoNode, FileNode]\n\nexport type MediaKind = 'image' | 'audio' | 'video' | 'file'\n\n/** Uploadable MIME types → file extension on disk and media kind. `<uuid>.<ext>` asset names derive from this. */\nexport const MEDIA_TYPES: Readonly<Record<string, { ext: string, kind: MediaKind }>> = {\n 'image/png': { ext: 'png', kind: 'image' },\n 'image/jpeg': { ext: 'jpg', kind: 'image' },\n 'image/gif': { ext: 'gif', kind: 'image' },\n 'image/webp': { ext: 'webp', kind: 'image' },\n 'audio/webm': { ext: 'weba', kind: 'audio' },\n 'audio/mp4': { ext: 'm4a', kind: 'audio' },\n // What Chrome on macOS reports for `.m4a` files picked from disk.\n 'audio/x-m4a': { ext: 'm4a', kind: 'audio' },\n 'audio/mpeg': { ext: 'mp3', kind: 'audio' },\n 'audio/wav': { ext: 'wav', kind: 'audio' },\n 'audio/x-wav': { ext: 'wav', kind: 'audio' },\n 'audio/ogg': { ext: 'ogg', kind: 'audio' },\n 'video/mp4': { ext: 'mp4', kind: 'video' },\n 'video/webm': { ext: 'webm', kind: 'video' },\n 'video/quicktime': { ext: 'mov', kind: 'video' },\n}\n\n/** Upload size limit per kind. */\nexport const MAX_MEDIA_BYTES: Readonly<Record<MediaKind, number>> = {\n image: 5 * 1024 * 1024,\n audio: 50 * 1024 * 1024,\n video: 200 * 1024 * 1024,\n // Generic files (PDF, office documents, …): anything the media kinds do not cover.\n file: 50 * 1024 * 1024,\n}\n\n/** `type` without parameters, lowercased (`audio/webm;codecs=opus` → `audio/webm`): the key into `MEDIA_TYPES`. */\nexport const mediaMime = (type: string): string => (type.split(';')[0] ?? '').trim().toLowerCase()\n\n/** Kind of an uploadable MIME type (parameters such as `;codecs=…` ignored); null when uploads do not accept it. */\nexport function mediaKindOf(mime: string): MediaKind | null {\n return MEDIA_TYPES[mediaMime(mime)]?.kind ?? null\n}\n\n/** `accept` value of a file input for one kind: its MIME types, comma-joined. */\nexport function acceptFor(kind: MediaKind): string {\n return Object.keys(MEDIA_TYPES).filter(mime => MEDIA_TYPES[mime]?.kind === kind).join(',')\n}\n\n/** Attributes of an audio/video node; `title` is the editable caption, `mimeType` the uploaded file's base type. */\nexport interface MediaAttrs {\n src: string\n title?: string\n mimeType?: string\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n audio: {\n /** Inserts an audio block at the selection. */\n insertAudio: (attrs: MediaAttrs) => ReturnType\n }\n video: {\n /** Inserts a video block at the selection. */\n insertVideo: (attrs: MediaAttrs) => ReturnType\n }\n }\n}\n\nconst mediaAttributes: Attributes = {\n src: { default: '' },\n title: {\n default: '',\n parseHTML: el => el.getAttribute('data-title') ?? '',\n renderHTML: attrs => ({ 'data-title': attrs.title }),\n },\n mimeType: {\n default: '',\n parseHTML: el => el.getAttribute('data-mime') ?? '',\n renderHTML: attrs => ({ 'data-mime': attrs.mimeType }),\n },\n}\n\n/** Markdown export (AI context, `getMarkdown()`): a link labelled with the kind and, when there is one, the title. */\nfunction mediaMarkdown(label: string, title: unknown, src: unknown): string {\n const text = title ? `${label}: ${String(title).replace(/[[\\]\\\\]/g, '\\\\$&')}` : label\n return `[${text}](${String(src ?? '')})`\n}\n\nexport const AudioNode = Node.create({\n name: 'audio',\n group: 'block',\n atom: true,\n draggable: true,\n // A player block holds no text: nothing text-related applies inside it.\n abilities: { revokes: TEXT_ABILITIES },\n addAttributes: () => mediaAttributes,\n parseHTML: () => [{ tag: 'audio[src]' }],\n renderHTML: ({ HTMLAttributes }) => ['audio', mergeAttributes(HTMLAttributes, { controls: 'true', preload: 'metadata' })],\n renderMarkdown: node => mediaMarkdown('Audio', node.attrs?.title || 'Aufnahme', node.attrs?.src),\n addCommands() {\n return {\n insertAudio: attrs => ({ commands }) => commands.insertContent({ type: this.name, attrs }),\n }\n },\n})\n\nexport const VideoNode = Node.create({\n name: 'video',\n group: 'block',\n atom: true,\n draggable: true,\n abilities: { revokes: TEXT_ABILITIES },\n addAttributes: () => mediaAttributes,\n parseHTML: () => [{ tag: 'video[src]' }],\n renderHTML: ({ HTMLAttributes }) => ['video', mergeAttributes(HTMLAttributes, { controls: 'true', preload: 'metadata' })],\n renderMarkdown: node => mediaMarkdown('Video', node.attrs?.title, node.attrs?.src),\n addCommands() {\n return {\n insertVideo: attrs => ({ commands }) => commands.insertContent({ type: this.name, attrs }),\n }\n },\n})\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n file: {\n /** Inserts a file block (download link, PDF preview) at the selection. */\n insertFile: (attrs: FileAttrs) => ReturnType\n }\n }\n}\n\n/** Attributes of a file block; `title` is the editable file name, `size` the byte count. */\nexport interface FileAttrs {\n src: string\n title?: string\n mimeType?: string\n size?: number\n}\n\n/** A generic uploaded file (PDF, office document, …) that has no player: shown as a card with an open button. */\nexport const FileNode = Node.create({\n name: 'file',\n group: 'block',\n atom: true,\n draggable: true,\n abilities: { revokes: TEXT_ABILITIES },\n addAttributes: () => ({\n src: { default: '', parseHTML: el => el.getAttribute('href') ?? '' },\n title: { default: '', parseHTML: el => el.getAttribute('download') ?? '' },\n mimeType: { default: '', parseHTML: el => el.getAttribute('data-mime') ?? '' },\n size: { default: 0, parseHTML: el => Number(el.getAttribute('data-size')) || 0 },\n }),\n parseHTML: () => [{ tag: 'a[data-type=\"file\"]' }],\n renderHTML: ({ node }) => ['a', {\n 'data-type': 'file',\n 'href': node.attrs.src,\n 'download': node.attrs.title,\n 'data-mime': node.attrs.mimeType,\n 'data-size': String(node.attrs.size),\n }, node.attrs.title || 'Datei'],\n renderMarkdown: node => `[Datei: ${String(node.attrs?.title || 'Datei').replace(/[[\\]\\\\]/g, '\\\\$&')}](${String(node.attrs?.src ?? '')})`,\n addCommands() {\n return {\n insertFile: attrs => ({ commands }) => commands.insertContent({ type: this.name, attrs }),\n }\n },\n})\n"],"mappings":";;;;;;AAUA,MAAa,8BAA8C;CAAC;CAAO;CAAW;CAAW;AAAQ;;AAKjG,MAAa,cAA0E;CACrF,aAAa;EAAE,KAAK;EAAO,MAAM;CAAQ;CACzC,cAAc;EAAE,KAAK;EAAO,MAAM;CAAQ;CAC1C,aAAa;EAAE,KAAK;EAAO,MAAM;CAAQ;CACzC,cAAc;EAAE,KAAK;EAAQ,MAAM;CAAQ;CAC3C,cAAc;EAAE,KAAK;EAAQ,MAAM;CAAQ;CAC3C,aAAa;EAAE,KAAK;EAAO,MAAM;CAAQ;CAEzC,eAAe;EAAE,KAAK;EAAO,MAAM;CAAQ;CAC3C,cAAc;EAAE,KAAK;EAAO,MAAM;CAAQ;CAC1C,aAAa;EAAE,KAAK;EAAO,MAAM;CAAQ;CACzC,eAAe;EAAE,KAAK;EAAO,MAAM;CAAQ;CAC3C,aAAa;EAAE,KAAK;EAAO,MAAM;CAAQ;CACzC,aAAa;EAAE,KAAK;EAAO,MAAM;CAAQ;CACzC,cAAc;EAAE,KAAK;EAAQ,MAAM;CAAQ;CAC3C,mBAAmB;EAAE,KAAK;EAAO,MAAM;CAAQ;AACjD;;AAGA,MAAa,kBAAuD;CAClE,OAAO;CACP,OAAO;CACP,OAAO;CAEP,MAAM;AACR;;AAGA,MAAa,aAAa,UAA0B,KAAK,MAAM,GAAG,CAAC,CAAC,MAAM,GAAA,CAAI,KAAK,CAAC,CAAC,YAAY;;AAGjG,SAAgB,YAAY,MAAgC;CAC1D,OAAO,YAAY,UAAU,IAAI,EAAE,EAAE,QAAQ;AAC/C;;AAGA,SAAgB,UAAU,MAAyB;CACjD,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,QAAO,SAAQ,YAAY,KAAK,EAAE,SAAS,IAAI,CAAC,CAAC,KAAK,GAAG;AAC3F;AAsBA,MAAM,kBAA8B;CAClC,KAAK,EAAE,SAAS,GAAG;CACnB,OAAO;EACL,SAAS;EACT,YAAW,OAAM,GAAG,aAAa,YAAY,KAAK;EAClD,aAAY,WAAU,EAAE,cAAc,MAAM,MAAM;CACpD;CACA,UAAU;EACR,SAAS;EACT,YAAW,OAAM,GAAG,aAAa,WAAW,KAAK;EACjD,aAAY,WAAU,EAAE,aAAa,MAAM,SAAS;CACtD;AACF;;AAGA,SAAS,cAAc,OAAe,OAAgB,KAAsB;CAE1E,OAAO,IADM,QAAQ,GAAG,MAAM,IAAI,OAAO,KAAK,CAAC,CAAC,QAAQ,YAAY,MAAM,MAAM,MAChE,IAAI,OAAO,OAAO,EAAE,EAAE;AACxC;AAEA,MAAa,YAAY,KAAK,OAAO;CACnC,MAAM;CACN,OAAO;CACP,MAAM;CACN,WAAW;CAEX,WAAW,EAAE,SAAS,eAAe;CACrC,qBAAqB;CACrB,iBAAiB,CAAC,EAAE,KAAK,aAAa,CAAC;CACvC,aAAa,EAAE,qBAAqB,CAAC,SAAS,gBAAgB,gBAAgB;EAAE,UAAU;EAAQ,SAAS;CAAW,CAAC,CAAC;CACxH,iBAAgB,SAAQ,cAAc,SAAS,KAAK,OAAO,SAAS,YAAY,KAAK,OAAO,GAAG;CAC/F,cAAc;EACZ,OAAO,EACL,cAAa,WAAU,EAAE,eAAe,SAAS,cAAc;GAAE,MAAM,KAAK;GAAM;EAAM,CAAC,EAC3F;CACF;AACF,CAAC;AAED,MAAa,YAAY,KAAK,OAAO;CACnC,MAAM;CACN,OAAO;CACP,MAAM;CACN,WAAW;CACX,WAAW,EAAE,SAAS,eAAe;CACrC,qBAAqB;CACrB,iBAAiB,CAAC,EAAE,KAAK,aAAa,CAAC;CACvC,aAAa,EAAE,qBAAqB,CAAC,SAAS,gBAAgB,gBAAgB;EAAE,UAAU;EAAQ,SAAS;CAAW,CAAC,CAAC;CACxH,iBAAgB,SAAQ,cAAc,SAAS,KAAK,OAAO,OAAO,KAAK,OAAO,GAAG;CACjF,cAAc;EACZ,OAAO,EACL,cAAa,WAAU,EAAE,eAAe,SAAS,cAAc;GAAE,MAAM,KAAK;GAAM;EAAM,CAAC,EAC3F;CACF;AACF,CAAC;;AAoBD,MAAa,WAAW,KAAK,OAAO;CAClC,MAAM;CACN,OAAO;CACP,MAAM;CACN,WAAW;CACX,WAAW,EAAE,SAAS,eAAe;CACrC,sBAAsB;EACpB,KAAK;GAAE,SAAS;GAAI,YAAW,OAAM,GAAG,aAAa,MAAM,KAAK;EAAG;EACnE,OAAO;GAAE,SAAS;GAAI,YAAW,OAAM,GAAG,aAAa,UAAU,KAAK;EAAG;EACzE,UAAU;GAAE,SAAS;GAAI,YAAW,OAAM,GAAG,aAAa,WAAW,KAAK;EAAG;EAC7E,MAAM;GAAE,SAAS;GAAG,YAAW,OAAM,OAAO,GAAG,aAAa,WAAW,CAAC,KAAK;EAAE;CACjF;CACA,iBAAiB,CAAC,EAAE,KAAK,wBAAsB,CAAC;CAChD,aAAa,EAAE,WAAW;EAAC;EAAK;GAC9B,aAAa;GACb,QAAQ,KAAK,MAAM;GACnB,YAAY,KAAK,MAAM;GACvB,aAAa,KAAK,MAAM;GACxB,aAAa,OAAO,KAAK,MAAM,IAAI;EACrC;EAAG,KAAK,MAAM,SAAS;CAAO;CAC9B,iBAAgB,SAAQ,WAAW,OAAO,KAAK,OAAO,SAAS,OAAO,CAAC,CAAC,QAAQ,YAAY,MAAM,EAAE,IAAI,OAAO,KAAK,OAAO,OAAO,EAAE,EAAE;CACtI,cAAc;EACZ,OAAO,EACL,aAAY,WAAU,EAAE,eAAe,SAAS,cAAc;GAAE,MAAM,KAAK;GAAM;EAAM,CAAC,EAC1F;CACF;AACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"schema.js","names":[],"sources":["../../../src/features/media/schema.ts"],"sourcesContent":["import { type Attributes, type AnyExtension, type MarkdownToken, type MarkdownTokenizer, mergeAttributes, Node } from '@tiptap/core'\nimport { Image } from '@tiptap/extension-image'\nimport { TEXT_ABILITIES } from '../../core/schema'\n\nexport * from './assets'\n\n// Vue-free and server-importable (`@ecsplico/editor/media/schema`): the upload API validates against these types, the\n// schema has the nodes. The client adds the node views in node-views.ts.\n\n/** SHA-256 (hex) of the uploaded bytes, kept on media blocks so derived content can tell when its source changed; null when unknown (memory backend, older documents). */\nconst hashAttribute: Attributes[string] = {\n default: null,\n parseHTML: el => el.getAttribute('data-hash'),\n renderHTML: attrs => (attrs.hash ? { 'data-hash': attrs.hash } : {}),\n}\n\n/** The media nodes (image, audio, video, file) of the document schema. */\nexport const mediaSchemaExtensions = (): AnyExtension[] => [ImageNode, AudioNode, VideoNode, FileNode]\n\n/**\n * The image node with a `hash` attribute. Markdown: ``, followed by ` <!-- {\"hash\":\"…\"} -->` when the\n * block has a hash; only that form is claimed by `imageBlockTokenizer` (a block-level token that still yields an `image`\n * token), a plain image keeps the stock inline parse.\n */\nexport const ImageNode = Image.extend({\n addAttributes() {\n return { ...this.parent?.(), hash: hashAttribute }\n },\n markdownTokenizer: {\n name: 'imageBlock',\n level: 'block',\n start: src => src.search(new RegExp(IMAGE_BLOCK_PATTERN, 'm')),\n tokenize: (src) => {\n const match = new RegExp(IMAGE_BLOCK_PATTERN).exec(src)\n if (!match) return undefined\n return { type: 'image', raw: match[0], text: match[1] ?? '', href: match[2] ?? '', title: match[3], attrs: parseAttrComment(match[4]) }\n },\n },\n parseMarkdown: (token, helpers) => helpers.createNode('image', {\n src: token.href,\n title: token.title,\n alt: token.text,\n hash: typeof token.attrs?.hash === 'string' ? token.attrs.hash : null,\n }),\n renderMarkdown: (node) => {\n const { src = '', alt = '', title = '' } = node.attrs ?? {}\n return `${attrComment({ hash: node.attrs?.hash })}`\n },\n})\n\n// Markdown form of the text-less media blocks. Audio, video and file are a paragraph of nothing but a link\n// (`[Audio: name](url)`), optionally followed by an HTML comment holding the attributes the link cannot carry\n// (`hash`, `mimeType`, `size`) as JSON, the way composite fences hold their meta. The block-level tokenizers below parse\n// that line back into the node; labels of every UI language are accepted, the export always writes the German one.\nconst ATTR_COMMENT_PATTERN = '(?: <!-- (\\\\{.*?\\\\}) -->)?[ \\\\t]*(?:\\\\n|$)'\nconst IMAGE_BLOCK_PATTERN = `^!\\\\[([^\\\\]\\\\n]*)\\\\]\\\\(([^)\\\\s]*)(?: \"([^\"\\\\n]*)\")?\\\\) <!-- (\\\\{.*?\\\\}) -->[ \\\\t]*(?:\\\\n|$)`\n\n/** ` <!-- {\"hash\":\"…\"} -->` for the attributes that are set (empty string when none); `>` is escaped so the JSON cannot end the comment. */\nfunction attrComment(attrs: Record<string, unknown>): string {\n const set = Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== null && value !== undefined && value !== '' && value !== 0))\n return Object.keys(set).length ? ` <!-- ${JSON.stringify(set).replace(/>/g, '\\\\u003e')} -->` : ''\n}\n\n/** The attributes of an `attrComment`; empty for a missing or malformed one. */\nfunction parseAttrComment(json: string | undefined): Record<string, unknown> {\n if (!json) return {}\n try {\n const value: unknown = JSON.parse(json)\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n }\n catch {\n return {}\n }\n}\n\n/** Block tokenizer for `[<label>: title](src)` lines of one media node; `labels` are the accepted labels (regex alternatives). */\nfunction mediaTokenizer(name: string, labels: string): MarkdownTokenizer {\n const pattern = `^\\\\[(?:${labels})(?:: ((?:\\\\\\\\.|[^\\\\]\\\\\\\\\\\\n])*))?\\\\]\\\\(([^)\\\\s]*)\\\\)${ATTR_COMMENT_PATTERN}`\n return {\n name,\n level: 'block',\n start: src => src.search(new RegExp(pattern, 'm')),\n tokenize: (src) => {\n const match = new RegExp(pattern).exec(src)\n if (!match) return undefined\n return { type: name, raw: match[0], title: (match[1] ?? '').replace(/\\\\(.)/g, '$1'), href: match[2] ?? '', attrs: parseAttrComment(match[3]) }\n },\n }\n}\n\nconst stringAttr = (value: unknown): string => typeof value === 'string' ? value : ''\n\nexport type MediaKind = 'image' | 'audio' | 'video' | 'file'\n\n/** Uploadable MIME types → file extension on disk and media kind. `<uuid>.<ext>` asset names derive from this. */\nexport const MEDIA_TYPES: Readonly<Record<string, { ext: string, kind: MediaKind }>> = {\n 'image/png': { ext: 'png', kind: 'image' },\n 'image/jpeg': { ext: 'jpg', kind: 'image' },\n 'image/gif': { ext: 'gif', kind: 'image' },\n 'image/webp': { ext: 'webp', kind: 'image' },\n 'audio/webm': { ext: 'weba', kind: 'audio' },\n 'audio/mp4': { ext: 'm4a', kind: 'audio' },\n // What Chrome on macOS reports for `.m4a` files picked from disk.\n 'audio/x-m4a': { ext: 'm4a', kind: 'audio' },\n 'audio/mpeg': { ext: 'mp3', kind: 'audio' },\n 'audio/wav': { ext: 'wav', kind: 'audio' },\n 'audio/x-wav': { ext: 'wav', kind: 'audio' },\n 'audio/ogg': { ext: 'ogg', kind: 'audio' },\n 'video/mp4': { ext: 'mp4', kind: 'video' },\n 'video/webm': { ext: 'webm', kind: 'video' },\n 'video/quicktime': { ext: 'mov', kind: 'video' },\n}\n\n/** Upload size limit per kind. */\nexport const MAX_MEDIA_BYTES: Readonly<Record<MediaKind, number>> = {\n image: 5 * 1024 * 1024,\n audio: 50 * 1024 * 1024,\n video: 200 * 1024 * 1024,\n // Generic files (PDF, office documents, …): anything the media kinds do not cover.\n file: 50 * 1024 * 1024,\n}\n\n/** `type` without parameters, lowercased (`audio/webm;codecs=opus` → `audio/webm`): the key into `MEDIA_TYPES`. */\nexport const mediaMime = (type: string): string => (type.split(';')[0] ?? '').trim().toLowerCase()\n\n/** Kind of an uploadable MIME type (parameters such as `;codecs=…` ignored); null when uploads do not accept it. */\nexport function mediaKindOf(mime: string): MediaKind | null {\n return MEDIA_TYPES[mediaMime(mime)]?.kind ?? null\n}\n\n/** `accept` value of a file input for one kind: its MIME types, comma-joined. */\nexport function acceptFor(kind: MediaKind): string {\n return Object.keys(MEDIA_TYPES).filter(mime => MEDIA_TYPES[mime]?.kind === kind).join(',')\n}\n\n/** Attributes of an audio/video node; `title` is the editable caption, `mimeType` the uploaded file's base type. */\nexport interface MediaAttrs {\n src: string\n title?: string\n mimeType?: string\n hash?: string | null\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n audio: {\n /** Inserts an audio block at the selection. */\n insertAudio: (attrs: MediaAttrs) => ReturnType\n }\n video: {\n /** Inserts a video block at the selection. */\n insertVideo: (attrs: MediaAttrs) => ReturnType\n }\n }\n}\n\nconst mediaAttributes: Attributes = {\n src: { default: '' },\n title: {\n default: '',\n parseHTML: el => el.getAttribute('data-title') ?? '',\n renderHTML: attrs => ({ 'data-title': attrs.title }),\n },\n mimeType: {\n default: '',\n parseHTML: el => el.getAttribute('data-mime') ?? '',\n renderHTML: attrs => ({ 'data-mime': attrs.mimeType }),\n },\n hash: hashAttribute,\n}\n\n/** Markdown export (AI context, `getMarkdown()`, source mode): a link labelled with the kind and, when there is one, the title; `extra` and the hash go into the trailing attribute comment. */\nfunction mediaMarkdown(label: string, attrs: Record<string, unknown> | undefined, extra: Record<string, unknown>): string {\n const title = attrs?.title\n const text = title ? `${label}: ${String(title).replace(/[[\\]\\\\]/g, '\\\\$&')}` : label\n return `[${text}](${String(attrs?.src ?? '')})${attrComment({ ...extra, hash: attrs?.hash })}`\n}\n\n/** Node attributes of an audio/video Markdown token. */\nfunction mediaAttrsOfToken(token: MarkdownToken) {\n return { src: token.href, title: token.title, mimeType: stringAttr(token.attrs?.mimeType), hash: stringAttr(token.attrs?.hash) || null }\n}\n\nexport const AudioNode = Node.create({\n name: 'audio',\n group: 'block',\n atom: true,\n draggable: true,\n // A player block holds no text: nothing text-related applies inside it.\n abilities: { revokes: TEXT_ABILITIES },\n addAttributes: () => mediaAttributes,\n parseHTML: () => [{ tag: 'audio[src]' }],\n renderHTML: ({ HTMLAttributes }) => ['audio', mergeAttributes(HTMLAttributes, { controls: 'true', preload: 'metadata' })],\n markdownTokenizer: mediaTokenizer('audio', 'Audio'),\n parseMarkdown: (token, helpers) => helpers.createNode('audio', mediaAttrsOfToken(token)),\n renderMarkdown: node => mediaMarkdown('Audio', node.attrs, { mimeType: node.attrs?.mimeType }),\n addCommands() {\n return {\n insertAudio: attrs => ({ commands }) => commands.insertContent({ type: this.name, attrs }),\n }\n },\n})\n\nexport const VideoNode = Node.create({\n name: 'video',\n group: 'block',\n atom: true,\n draggable: true,\n abilities: { revokes: TEXT_ABILITIES },\n addAttributes: () => mediaAttributes,\n parseHTML: () => [{ tag: 'video[src]' }],\n renderHTML: ({ HTMLAttributes }) => ['video', mergeAttributes(HTMLAttributes, { controls: 'true', preload: 'metadata' })],\n markdownTokenizer: mediaTokenizer('video', 'Video'),\n parseMarkdown: (token, helpers) => helpers.createNode('video', mediaAttrsOfToken(token)),\n renderMarkdown: node => mediaMarkdown('Video', node.attrs, { mimeType: node.attrs?.mimeType }),\n addCommands() {\n return {\n insertVideo: attrs => ({ commands }) => commands.insertContent({ type: this.name, attrs }),\n }\n },\n})\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n file: {\n /** Inserts a file block (download link, PDF preview) at the selection. */\n insertFile: (attrs: FileAttrs) => ReturnType\n }\n }\n}\n\n/** Attributes of a file block; `title` is the editable file name, `size` the byte count. */\nexport interface FileAttrs {\n src: string\n title?: string\n mimeType?: string\n size?: number\n hash?: string | null\n}\n\n/** A generic uploaded file (PDF, office document, …) that has no player: shown as a card with an open button. */\nexport const FileNode = Node.create({\n name: 'file',\n group: 'block',\n atom: true,\n draggable: true,\n abilities: { revokes: TEXT_ABILITIES },\n addAttributes: () => ({\n src: { default: '', parseHTML: el => el.getAttribute('href') ?? '' },\n title: { default: '', parseHTML: el => el.getAttribute('download') ?? '' },\n mimeType: { default: '', parseHTML: el => el.getAttribute('data-mime') ?? '' },\n size: { default: 0, parseHTML: el => Number(el.getAttribute('data-size')) || 0 },\n hash: hashAttribute,\n }),\n parseHTML: () => [{ tag: 'a[data-type=\"file\"]' }],\n renderHTML: ({ node }) => ['a', {\n 'data-type': 'file',\n 'href': node.attrs.src,\n 'download': node.attrs.title,\n 'data-mime': node.attrs.mimeType,\n 'data-size': String(node.attrs.size),\n ...(node.attrs.hash ? { 'data-hash': node.attrs.hash } : {}),\n }, node.attrs.title || 'Datei'],\n markdownTokenizer: mediaTokenizer('file', 'Datei|File'),\n parseMarkdown: (token, helpers) => helpers.createNode('file', {\n src: token.href,\n title: token.title,\n mimeType: stringAttr(token.attrs?.mimeType),\n size: typeof token.attrs?.size === 'number' ? token.attrs.size : 0,\n hash: stringAttr(token.attrs?.hash) || null,\n }),\n renderMarkdown: node => mediaMarkdown('Datei', node.attrs, { mimeType: node.attrs?.mimeType, size: node.attrs?.size }),\n addCommands() {\n return {\n insertFile: attrs => ({ commands }) => commands.insertContent({ type: this.name, attrs }),\n }\n },\n})\n"],"mappings":"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"}
|
|
@@ -1,35 +1,36 @@
|
|
|
1
|
-
import { FeatureContext
|
|
1
|
+
import { FeatureContext } from "../../core/feature.js";
|
|
2
2
|
import "../../core/index.js";
|
|
3
3
|
import { Editor, Extension, JSONContent } from "@tiptap/core";
|
|
4
4
|
//#region src/features/media/upload.d.ts
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
/** Default upload: POSTs `file` to the asset endpoint of document `documentId` through the context's HTTP client (size limits per kind, see schema.ts). */
|
|
8
|
-
export declare function postUpload(fetch: FeatureContext['fetch'], documentId: string, file: File): Promise<{
|
|
5
|
+
/** Stores a file and resolves with the URL it is addressed by. */
|
|
6
|
+
type UploadFile = (file: File) => Promise<{
|
|
9
7
|
url: string;
|
|
8
|
+
sha256?: string;
|
|
10
9
|
}>;
|
|
11
|
-
/**
|
|
12
|
-
export
|
|
10
|
+
/** `importFiles` of the media feature: imports `files` at `pos` (default: the selection), toasting failures; resolves whether every file was imported. */
|
|
11
|
+
export type ImportFilesFn = (editor: Editor, files: File[] | FileList, pos?: number) => Promise<boolean>;
|
|
12
|
+
/** The upload of a surface: the backend's asset store for its `documentId` (default: the shared `scratch` owner; size limits per kind, see schema.ts). */
|
|
13
|
+
export declare const uploadOf: (ctx: FeatureContext) => UploadFile;
|
|
13
14
|
/** Makes the upload and the file import reachable as `editor.storage.media` (drop/paste, toolbar, slash menu, recorder). */
|
|
14
15
|
export declare const Media: Extension<{
|
|
15
|
-
upload:
|
|
16
|
+
upload: UploadFile;
|
|
16
17
|
importFiles: ImportFilesFn;
|
|
17
18
|
}, {
|
|
18
|
-
upload:
|
|
19
|
+
upload: UploadFile;
|
|
19
20
|
importFiles: ImportFilesFn;
|
|
20
21
|
}>;
|
|
21
22
|
declare module '@tiptap/core' {
|
|
22
23
|
interface Storage {
|
|
23
24
|
media: {
|
|
24
|
-
upload:
|
|
25
|
+
upload: UploadFile;
|
|
25
26
|
importFiles: ImportFilesFn;
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
|
-
/** The node that embeds uploaded `file` from `url
|
|
30
|
-
export declare function mediaNodeFor(file: File, url: string): JSONContent | null;
|
|
31
|
-
/** The file block for an uploaded `file` that has no player or image node (PDF, office documents, …)
|
|
32
|
-
export declare function fileNodeFor(file: File, url: string): JSONContent;
|
|
30
|
+
/** The node that embeds uploaded `file` from `url`, remembering the upload's `hash` (SHA-256 hex; none when the backend did not report one); null for files uploads do not accept. */
|
|
31
|
+
export declare function mediaNodeFor(file: File, url: string, hash?: string): JSONContent | null;
|
|
32
|
+
/** The file block for an uploaded `file` that has no player or image node (PDF, office documents, …); `hash` as in `mediaNodeFor`. */
|
|
33
|
+
export declare function fileNodeFor(file: File, url: string, hash?: string): JSONContent;
|
|
33
34
|
/** Human-readable byte count: 1024-based, one decimal (`1536` → `1.5 KB`), units B/KB/MB/GB. */
|
|
34
35
|
export declare function formatBytes(bytes: number): string;
|
|
35
36
|
/** Transaction meta the recorder modal (AudioRecorderModal) listens for: open it, inserting at the current caret. */
|
|
@@ -38,4 +39,5 @@ export declare function openAudioRecorder(editor: Editor): void;
|
|
|
38
39
|
/** Opens the browser's file picker; resolves the chosen files, `[]` when the picker is cancelled. */
|
|
39
40
|
export declare function pickFiles(accept: string, multiple?: boolean): Promise<File[]>;
|
|
40
41
|
//#endregion
|
|
42
|
+
export {};
|
|
41
43
|
//# sourceMappingURL=upload.d.ts.map
|
|
@@ -2,17 +2,8 @@ import "./assets.js";
|
|
|
2
2
|
import { mediaKindOf, mediaMime } from "./schema.js";
|
|
3
3
|
import { Extension } from "@tiptap/core";
|
|
4
4
|
//#region src/features/media/upload.ts
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
const body = new FormData();
|
|
8
|
-
body.append("file", file);
|
|
9
|
-
return await fetch(`/api/editor/assets/${documentId}`, {
|
|
10
|
-
method: "POST",
|
|
11
|
-
body
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
/** The upload of a surface: the host's `upload` prop, else the asset endpoint of its `documentId` (default: the shared `scratch` owner). */
|
|
15
|
-
const uploadOf = (ctx) => ctx.upload ?? ((file) => postUpload(ctx.fetch, ctx.documentId ?? "scratch", file));
|
|
5
|
+
/** The upload of a surface: the backend's asset store for its `documentId` (default: the shared `scratch` owner; size limits per kind, see schema.ts). */
|
|
6
|
+
const uploadOf = (ctx) => (file) => ctx.backend.assets ? ctx.backend.assets.upload(ctx.documentId ?? "scratch", file) : Promise.reject(/* @__PURE__ */ new Error(`@ecsplico/editor: backend "${ctx.backend.name}" has no assets capability`));
|
|
16
7
|
/** Makes the upload and the file import reachable as `editor.storage.media` (drop/paste, toolbar, slash menu, recorder). */
|
|
17
8
|
const Media = Extension.create({
|
|
18
9
|
name: "media",
|
|
@@ -29,15 +20,16 @@ const Media = Extension.create({
|
|
|
29
20
|
};
|
|
30
21
|
}
|
|
31
22
|
});
|
|
32
|
-
/** The node that embeds uploaded `file` from `url
|
|
33
|
-
function mediaNodeFor(file, url) {
|
|
23
|
+
/** The node that embeds uploaded `file` from `url`, remembering the upload's `hash` (SHA-256 hex; none when the backend did not report one); null for files uploads do not accept. */
|
|
24
|
+
function mediaNodeFor(file, url, hash) {
|
|
34
25
|
const kind = mediaKindOf(file.type);
|
|
35
26
|
if (!kind) return null;
|
|
36
27
|
if (kind === "image") return {
|
|
37
28
|
type: "image",
|
|
38
29
|
attrs: {
|
|
39
30
|
src: url,
|
|
40
|
-
alt: file.name
|
|
31
|
+
alt: file.name,
|
|
32
|
+
hash: hash ?? null
|
|
41
33
|
}
|
|
42
34
|
};
|
|
43
35
|
return {
|
|
@@ -45,19 +37,21 @@ function mediaNodeFor(file, url) {
|
|
|
45
37
|
attrs: {
|
|
46
38
|
src: url,
|
|
47
39
|
title: file.name,
|
|
48
|
-
mimeType: mediaMime(file.type)
|
|
40
|
+
mimeType: mediaMime(file.type),
|
|
41
|
+
hash: hash ?? null
|
|
49
42
|
}
|
|
50
43
|
};
|
|
51
44
|
}
|
|
52
|
-
/** The file block for an uploaded `file` that has no player or image node (PDF, office documents, …)
|
|
53
|
-
function fileNodeFor(file, url) {
|
|
45
|
+
/** The file block for an uploaded `file` that has no player or image node (PDF, office documents, …); `hash` as in `mediaNodeFor`. */
|
|
46
|
+
function fileNodeFor(file, url, hash) {
|
|
54
47
|
return {
|
|
55
48
|
type: "file",
|
|
56
49
|
attrs: {
|
|
57
50
|
src: url,
|
|
58
51
|
title: file.name,
|
|
59
52
|
mimeType: mediaMime(file.type) || "application/octet-stream",
|
|
60
|
-
size: file.size
|
|
53
|
+
size: file.size,
|
|
54
|
+
hash: hash ?? null
|
|
61
55
|
}
|
|
62
56
|
};
|
|
63
57
|
}
|
|
@@ -101,6 +95,6 @@ function pickFiles(accept, multiple = true) {
|
|
|
101
95
|
return promise;
|
|
102
96
|
}
|
|
103
97
|
//#endregion
|
|
104
|
-
export { AUDIO_RECORDER_OPEN, Media, fileNodeFor, formatBytes, mediaNodeFor, openAudioRecorder, pickFiles,
|
|
98
|
+
export { AUDIO_RECORDER_OPEN, Media, fileNodeFor, formatBytes, mediaNodeFor, openAudioRecorder, pickFiles, uploadOf };
|
|
105
99
|
|
|
106
100
|
//# sourceMappingURL=upload.js.map
|