@ecsplico/editor 3.6.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/dist/adapters/direct-ai.d.ts +4 -4
- package/dist/adapters/direct-ai.js +2 -2
- package/dist/adapters/direct-ai.js.map +1 -1
- package/dist/adapters/document-state.d.ts +1 -1
- package/dist/adapters/document-state.js.map +1 -1
- package/dist/adapters/forge.d.ts +2 -2
- package/dist/adapters/forge.js.map +1 -1
- package/dist/adapters/nitro.d.ts +11 -3
- package/dist/adapters/nitro.js +3 -1
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/shared.d.ts +6 -6
- package/dist/adapters/shared.js.map +1 -1
- package/dist/code/CodeSurface.vue.d.ts +3 -3
- package/dist/config/index.d.ts +14 -14
- package/dist/config/index.js +8 -8
- package/dist/config/index.js.map +1 -1
- package/dist/core/backend-types.d.ts +3 -3
- package/dist/core/backend-types.js.map +1 -1
- package/dist/core/backend.d.ts +4 -4
- package/dist/core/backend.js.map +1 -1
- package/dist/core/block-convert.d.ts +1 -1
- package/dist/core/block-convert.js +5 -5
- package/dist/core/block-convert.js.map +1 -1
- package/dist/core/block-transfer.d.ts +8 -4
- package/dist/core/block-transfer.js +23 -14
- package/dist/core/block-transfer.js.map +1 -1
- package/dist/core/capabilities.d.ts +113 -0
- package/dist/core/capabilities.js +155 -0
- package/dist/core/capabilities.js.map +1 -0
- package/dist/core/command-types.d.ts +1 -1
- package/dist/core/context.js.map +1 -1
- package/dist/core/convert.d.ts +5 -5
- package/dist/core/convert.js +5 -5
- package/dist/core/convert.js.map +1 -1
- package/dist/core/core-feature.js +1 -1
- package/dist/core/define-editor.d.ts +2 -2
- package/dist/core/define-editor.js +34 -6
- package/dist/core/define-editor.js.map +1 -1
- package/dist/core/derivation.d.ts +119 -0
- package/dist/core/derivation.js +187 -0
- package/dist/core/derivation.js.map +1 -0
- package/dist/core/derive.d.ts +61 -0
- package/dist/core/derive.js +69 -0
- package/dist/core/derive.js.map +1 -0
- package/dist/core/extension-points.d.ts +16 -0
- package/dist/core/feature-context.d.ts +5 -1
- package/dist/core/feature-context.js +10 -3
- package/dist/core/feature-context.js.map +1 -1
- package/dist/core/feature.d.ts +41 -19
- package/dist/core/feature.js.map +1 -1
- package/dist/core/fences.js +1 -1
- package/dist/core/fences.js.map +1 -1
- package/dist/core/index.d.ts +6 -2
- package/dist/core/index.js +5 -1
- package/dist/core/pipeline.d.ts +14 -5
- package/dist/core/pipeline.js +19 -7
- package/dist/core/pipeline.js.map +1 -1
- package/dist/core/properties.d.ts +22 -0
- package/dist/core/properties.js +17 -0
- package/dist/core/properties.js.map +1 -0
- package/dist/core/schema/block-ids.js +5 -5
- package/dist/core/schema/block-ids.js.map +1 -1
- package/dist/core/schema/index.d.ts +2 -1
- package/dist/core/schema/index.js +2 -1
- package/dist/core/schema/index.js.map +1 -1
- package/dist/core/schema/opaque-fence.d.ts +4 -4
- package/dist/core/schema/opaque-fence.js +7 -7
- package/dist/core/schema/opaque-fence.js.map +1 -1
- package/dist/core/schema/persisted-names.d.ts +58 -0
- package/dist/core/schema/persisted-names.js +96 -0
- package/dist/core/schema/persisted-names.js.map +1 -0
- package/dist/core/toolbar-items.js +1 -1
- package/dist/core/toolbar-items.js.map +1 -1
- package/dist/core/util.js +5 -1
- package/dist/core/util.js.map +1 -1
- package/dist/document/DocumentSurface.vue.d.ts +3 -3
- package/dist/features/ai/agent-ops.js +1 -1
- package/dist/features/ai/ai-review.js +1 -1
- package/dist/features/ai/assistant.d.ts +4 -4
- package/dist/features/ai/assistant.js +1 -1
- package/dist/features/ai/assistant.js.map +1 -1
- package/dist/features/ai/components/AiSelectionMenu.js +1 -1
- package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
- package/dist/features/ai/components/ModelQuickSwitch.js +7 -7
- package/dist/features/ai/components/ModelQuickSwitch.js.map +1 -1
- package/dist/features/ai/components/ModelQuickSwitch.vue.d.ts +4 -4
- package/dist/features/ai/components/SettingsAiTab.js +8 -8
- package/dist/features/ai/components/SettingsAiTab.js.map +1 -1
- package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/components/SettingsModelProfiles.js.map +1 -1
- package/dist/features/ai/document-chat-model.d.ts +2 -2
- 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 +2 -2
- package/dist/features/ai/document-chat.js.map +1 -1
- package/dist/features/ai/feature.d.ts +2 -2
- package/dist/features/ai/feature.js +2 -2
- package/dist/features/ai/feature.js.map +1 -1
- package/dist/features/ai/ghost-autocomplete.d.ts +4 -4
- package/dist/features/ai/ghost-autocomplete.js +1 -1
- package/dist/features/ai/ghost-autocomplete.js.map +1 -1
- package/dist/features/ai/index.d.ts +3 -3
- package/dist/features/ai/index.js +2 -2
- package/dist/features/ai/model-profiles.d.ts +9 -9
- package/dist/features/ai/model-profiles.js +13 -13
- package/dist/features/ai/model-profiles.js.map +1 -1
- package/dist/features/ai/protocol/agent-ops.d.ts +2 -2
- package/dist/features/ai/protocol/agent-ops.js +1 -1
- package/dist/features/ai/protocol/agent-ops.js.map +1 -1
- package/dist/features/ai/protocol/block-lines.d.ts +7 -7
- package/dist/features/ai/protocol/block-lines.js +10 -10
- package/dist/features/ai/protocol/block-lines.js.map +1 -1
- package/dist/features/ai/protocol/chat.d.ts +5 -5
- package/dist/features/ai/protocol/chat.js +12 -12
- package/dist/features/ai/protocol/chat.js.map +1 -1
- package/dist/features/ai/protocol/generate.d.ts +1 -1
- package/dist/features/ai/protocol/generate.js +1 -1
- package/dist/features/ai/protocol/generate.js.map +1 -1
- package/dist/features/ai/protocol/index.d.ts +3 -3
- package/dist/features/ai/protocol/index.js +2 -2
- package/dist/features/ai/protocol/models.d.ts +3 -3
- package/dist/features/ai/protocol/models.js +3 -3
- package/dist/features/ai/protocol/models.js.map +1 -1
- package/dist/features/ai/protocol/schemas.d.ts +1 -1
- package/dist/features/ai/protocol/schemas.js +1 -1
- package/dist/features/ai/protocol/schemas.js.map +1 -1
- package/dist/features/ai/protocol/tasks.d.ts +5 -5
- package/dist/features/ai/protocol/tasks.js +12 -12
- package/dist/features/ai/protocol/tasks.js.map +1 -1
- package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
- package/dist/features/ai/react/ModelQuickSwitch.d.ts +4 -4
- package/dist/features/ai/react/ModelQuickSwitch.js +6 -6
- package/dist/features/ai/react/ModelQuickSwitch.js.map +1 -1
- package/dist/features/ai/react/SettingsAiTab.d.ts +1 -1
- package/dist/features/ai/react/SettingsAiTab.js +8 -8
- package/dist/features/ai/react/SettingsAiTab.js.map +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.d.ts +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
- 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 +1 -1
- package/dist/features/ai/react/use-ai-assistant.js.map +1 -1
- package/dist/features/ai/react/use-model-profiles.d.ts +5 -5
- package/dist/features/ai/react/use-model-profiles.js +2 -2
- package/dist/features/ai/react/use-model-profiles.js.map +1 -1
- package/dist/features/ai/use-ai-assistant.d.ts +1 -1
- package/dist/features/ai/use-ai-assistant.js +1 -1
- package/dist/features/ai/use-ai-assistant.js.map +1 -1
- package/dist/features/ai/use-model-profiles.d.ts +4 -4
- package/dist/features/ai/use-model-profiles.js +2 -2
- package/dist/features/ai/use-model-profiles.js.map +1 -1
- package/dist/features/block-ui/block-actions.d.ts +8 -4
- package/dist/features/block-ui/block-actions.js +38 -22
- package/dist/features/block-ui/block-actions.js.map +1 -1
- package/dist/features/block-ui/block-target.d.ts +29 -0
- package/dist/features/block-ui/block-target.js +63 -0
- package/dist/features/block-ui/block-target.js.map +1 -0
- package/dist/features/block-ui/components/BlockHandles.js +3 -3
- package/dist/features/block-ui/components/BlockHandles.js.map +1 -1
- package/dist/features/block-ui/components/BlockUi.js +11 -23
- package/dist/features/block-ui/components/BlockUi.js.map +1 -1
- package/dist/features/block-ui/feature.d.ts +3 -2
- package/dist/features/block-ui/feature.js +3 -2
- package/dist/features/block-ui/feature.js.map +1 -1
- package/dist/features/block-ui/handle-geometry.js +12 -23
- package/dist/features/block-ui/handle-geometry.js.map +1 -1
- package/dist/features/block-ui/index.d.ts +2 -1
- package/dist/features/block-ui/index.js +2 -1
- package/dist/features/block-ui/react/BlockHandles.d.ts +1 -1
- package/dist/features/block-ui/react/BlockHandles.js +4 -4
- package/dist/features/block-ui/react/BlockHandles.js.map +1 -1
- package/dist/features/block-ui/react/BlockUi.js +13 -25
- package/dist/features/block-ui/react/BlockUi.js.map +1 -1
- package/dist/features/comments/model.js +1 -1
- package/dist/features/composites/accepted-children.d.ts +5 -4
- package/dist/features/composites/accepted-children.js +26 -26
- package/dist/features/composites/accepted-children.js.map +1 -1
- package/dist/features/composites/ai-brief.d.ts +4 -4
- package/dist/features/composites/ai-brief.js +14 -12
- package/dist/features/composites/ai-brief.js.map +1 -1
- package/dist/features/composites/anchors.d.ts +1 -1
- package/dist/features/composites/anchors.js.map +1 -1
- package/dist/features/composites/baskets.d.ts +1 -1
- package/dist/features/composites/baskets.js.map +1 -1
- package/dist/features/composites/bibliography-command.d.ts +1 -1
- package/dist/features/composites/bibliography-form.js +1 -1
- package/dist/features/composites/bibliography-form.js.map +1 -1
- package/dist/features/composites/bibliography.js.map +1 -1
- package/dist/features/composites/block-types-json.d.ts +38 -0
- package/dist/features/composites/block-types-json.js +122 -0
- package/dist/features/composites/block-types-json.js.map +1 -0
- package/dist/features/composites/block-types.d.ts +119 -0
- package/dist/features/composites/block-types.js +343 -0
- package/dist/features/composites/block-types.js.map +1 -0
- package/dist/features/composites/capabilities.js +109 -0
- package/dist/features/composites/capabilities.js.map +1 -0
- package/dist/features/composites/components/BibliographyEditor.js +5 -5
- package/dist/features/composites/components/BibliographyEditor.js.map +1 -1
- package/dist/features/composites/components/CompositeBasketMenu.js +2 -2
- package/dist/features/composites/components/CompositeBasketMenu.js.map +1 -1
- package/dist/features/composites/components/CompositeChrome.js +18 -8
- package/dist/features/composites/components/CompositeChrome.js.map +1 -1
- package/dist/features/composites/components/CompositeChrome.vue.d.ts +1 -1
- package/dist/features/composites/components/CompositeDetails.js +11 -10
- package/dist/features/composites/components/CompositeDetails.js.map +1 -1
- package/dist/features/composites/components/CompositeDetails.vue.d.ts +1 -1
- package/dist/features/composites/components/CompositeFieldEditor.js +7 -7
- package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
- package/dist/features/composites/components/CompositeFieldEditor.vue.d.ts +5 -5
- package/dist/features/composites/components/CompositeInspector.js +136 -113
- package/dist/features/composites/components/CompositeInspector.js.map +1 -1
- package/dist/features/composites/components/CompositeInspector.vue.d.ts +3 -3
- package/dist/features/composites/components/CompositeStatusMenu.js +2 -2
- package/dist/features/composites/components/CompositeStatusMenu.js.map +1 -1
- package/dist/features/composites/components/CompositeSummaryEditor.js +1 -1
- package/dist/features/composites/components/CompositeSummaryEditor.js.map +1 -1
- package/dist/features/composites/components/CompositeSummaryEditor.vue.d.ts +1 -1
- package/dist/features/composites/components/CompositeTable.js +2 -2
- package/dist/features/composites/components/CompositeTable.js.map +1 -1
- package/dist/features/composites/components/CompositeTagsEditor.js +1 -1
- package/dist/features/composites/components/CompositeTagsEditor.js.map +1 -1
- package/dist/features/composites/components/CompositeTagsEditor.vue.d.ts +1 -1
- package/dist/features/composites/components/CompositeView.js +19 -17
- package/dist/features/composites/components/CompositeView.js.map +1 -1
- package/dist/features/composites/components/DerivationBadge.js +15 -6
- package/dist/features/composites/components/DerivationBadge.js.map +1 -1
- package/dist/features/composites/components/FocusChip.js +1 -1
- package/dist/features/composites/components/FocusChip.js.map +1 -1
- package/dist/features/composites/components/FocusChip.vue.d.ts +1 -1
- package/dist/features/composites/components/ReferenceFieldSelect.js +3 -3
- package/dist/features/composites/components/ReferenceFieldSelect.js.map +1 -1
- package/dist/features/composites/components/ReferenceView.js +1 -1
- package/dist/features/composites/components/ReferenceView.js.map +1 -1
- package/dist/features/composites/components/SettingsEditingTab.js +18 -18
- package/dist/features/composites/components/SettingsEditingTab.js.map +1 -1
- package/dist/features/composites/components/SettingsEditingTab.vue.d.ts +3 -3
- package/dist/features/composites/convert.d.ts +4 -4
- package/dist/features/composites/convert.js +27 -28
- package/dist/features/composites/convert.js.map +1 -1
- package/dist/features/composites/derived-chrome.js +34 -28
- package/dist/features/composites/derived-chrome.js.map +1 -1
- package/dist/features/composites/editing-settings-keys.js +6 -6
- package/dist/features/composites/editing-settings-keys.js.map +1 -1
- package/dist/features/composites/feature.d.ts +10 -10
- package/dist/features/composites/feature.js +58 -46
- package/dist/features/composites/feature.js.map +1 -1
- package/dist/features/composites/fences.d.ts +3 -3
- package/dist/features/composites/fences.js +18 -13
- package/dist/features/composites/fences.js.map +1 -1
- package/dist/features/composites/focus.d.ts +10 -10
- package/dist/features/composites/focus.js +11 -11
- package/dist/features/composites/focus.js.map +1 -1
- package/dist/features/composites/heading-composites.d.ts +5 -5
- package/dist/features/composites/heading-composites.js +13 -16
- package/dist/features/composites/heading-composites.js.map +1 -1
- package/dist/features/composites/index.d.ts +20 -18
- package/dist/features/composites/index.js +9 -7
- package/dist/features/composites/inspector-model.d.ts +45 -22
- package/dist/features/composites/inspector-model.js +53 -14
- package/dist/features/composites/inspector-model.js.map +1 -1
- package/dist/features/composites/legacy.d.ts +5 -4
- package/dist/features/composites/legacy.js +16 -23
- package/dist/features/composites/legacy.js.map +1 -1
- package/dist/features/composites/markdown-export.js +3 -3
- package/dist/features/composites/markdown-export.js.map +1 -1
- package/dist/features/composites/merge.d.ts +8 -8
- package/dist/features/composites/merge.js +23 -22
- package/dist/features/composites/merge.js.map +1 -1
- package/dist/features/composites/meta-display.js +5 -5
- package/dist/features/composites/meta-display.js.map +1 -1
- package/dist/features/composites/meta-keys.d.ts +5 -5
- package/dist/features/composites/meta-keys.js +8 -8
- package/dist/features/composites/meta-keys.js.map +1 -1
- package/dist/features/composites/node-views.d.ts +3 -3
- package/dist/features/composites/node-views.js +2 -2
- package/dist/features/composites/node-views.js.map +1 -1
- package/dist/features/composites/nodes.d.ts +13 -13
- package/dist/features/composites/nodes.js +100 -77
- package/dist/features/composites/nodes.js.map +1 -1
- package/dist/features/composites/outline.d.ts +15 -12
- package/dist/features/composites/outline.js +24 -21
- package/dist/features/composites/outline.js.map +1 -1
- package/dist/features/composites/properties.d.ts +51 -0
- package/dist/features/composites/properties.js +89 -0
- package/dist/features/composites/properties.js.map +1 -0
- package/dist/features/composites/react/BibliographyEditor.js +5 -5
- package/dist/features/composites/react/BibliographyEditor.js.map +1 -1
- package/dist/features/composites/react/CompositeBasketMenu.js +2 -2
- package/dist/features/composites/react/CompositeBasketMenu.js.map +1 -1
- package/dist/features/composites/react/CompositeChrome.d.ts +1 -1
- package/dist/features/composites/react/CompositeChrome.js +15 -6
- package/dist/features/composites/react/CompositeChrome.js.map +1 -1
- package/dist/features/composites/react/CompositeDetails.d.ts +1 -1
- package/dist/features/composites/react/CompositeDetails.js +11 -10
- package/dist/features/composites/react/CompositeDetails.js.map +1 -1
- package/dist/features/composites/react/CompositeFieldEditor.d.ts +5 -5
- package/dist/features/composites/react/CompositeFieldEditor.js +6 -6
- package/dist/features/composites/react/CompositeFieldEditor.js.map +1 -1
- package/dist/features/composites/react/CompositeInspector.d.ts +3 -3
- package/dist/features/composites/react/CompositeInspector.js +142 -128
- package/dist/features/composites/react/CompositeInspector.js.map +1 -1
- package/dist/features/composites/react/CompositeStatusMenu.js +2 -2
- package/dist/features/composites/react/CompositeStatusMenu.js.map +1 -1
- package/dist/features/composites/react/CompositeSummaryEditor.d.ts +1 -1
- package/dist/features/composites/react/CompositeSummaryEditor.js +1 -1
- package/dist/features/composites/react/CompositeSummaryEditor.js.map +1 -1
- package/dist/features/composites/react/CompositeTable.d.ts +1 -1
- package/dist/features/composites/react/CompositeTable.js +3 -3
- package/dist/features/composites/react/CompositeTable.js.map +1 -1
- package/dist/features/composites/react/CompositeTagsEditor.d.ts +1 -1
- package/dist/features/composites/react/CompositeTagsEditor.js +1 -1
- package/dist/features/composites/react/CompositeTagsEditor.js.map +1 -1
- package/dist/features/composites/react/CompositeView.js +17 -15
- package/dist/features/composites/react/CompositeView.js.map +1 -1
- package/dist/features/composites/react/DerivationBadge.js +13 -4
- package/dist/features/composites/react/DerivationBadge.js.map +1 -1
- package/dist/features/composites/react/FocusChip.d.ts +1 -1
- package/dist/features/composites/react/FocusChip.js +1 -1
- package/dist/features/composites/react/FocusChip.js.map +1 -1
- package/dist/features/composites/react/ReferenceFieldSelect.js +3 -3
- package/dist/features/composites/react/ReferenceFieldSelect.js.map +1 -1
- package/dist/features/composites/react/ReferenceView.d.ts +1 -1
- package/dist/features/composites/react/ReferenceView.js +1 -1
- package/dist/features/composites/react/ReferenceView.js.map +1 -1
- package/dist/features/composites/react/SettingsEditingTab.d.ts +4 -4
- package/dist/features/composites/react/SettingsEditingTab.js +20 -20
- package/dist/features/composites/react/SettingsEditingTab.js.map +1 -1
- package/dist/features/composites/react.js +1 -1
- package/dist/features/composites/react.js.map +1 -1
- package/dist/features/composites/reference-ai.js +1 -1
- package/dist/features/composites/reference-ai.js.map +1 -1
- package/dist/features/composites/reference-label.d.ts +3 -3
- package/dist/features/composites/reference-label.js +6 -6
- package/dist/features/composites/reference-label.js.map +1 -1
- package/dist/features/composites/reference-suggestion.d.ts +1 -1
- package/dist/features/composites/reference-suggestion.js +3 -3
- package/dist/features/composites/reference-suggestion.js.map +1 -1
- package/dist/features/composites/reference-text.d.ts +2 -2
- package/dist/features/composites/reference-text.js +4 -4
- package/dist/features/composites/reference-text.js.map +1 -1
- package/dist/features/composites/reference.d.ts +3 -3
- package/dist/features/composites/reference.js +1 -1
- package/dist/features/composites/reference.js.map +1 -1
- package/dist/features/composites/reveal-source.d.ts +3 -3
- package/dist/features/composites/reveal-source.js +6 -6
- package/dist/features/composites/reveal-source.js.map +1 -1
- package/dist/features/composites/rows-model.d.ts +2 -2
- package/dist/features/composites/rows-model.js +4 -3
- package/dist/features/composites/rows-model.js.map +1 -1
- package/dist/features/composites/rows.d.ts +16 -28
- package/dist/features/composites/rows.js +20 -54
- package/dist/features/composites/rows.js.map +1 -1
- package/dist/features/composites/schema.d.ts +12 -11
- package/dist/features/composites/schema.js +10 -9
- package/dist/features/composites/schema.js.map +1 -1
- package/dist/features/composites/source.d.ts +20 -0
- package/dist/features/composites/source.js +40 -0
- package/dist/features/composites/source.js.map +1 -0
- package/dist/features/composites/split.d.ts +11 -11
- package/dist/features/composites/split.js +20 -20
- package/dist/features/composites/split.js.map +1 -1
- package/dist/features/composites/structure-mode.js +3 -3
- package/dist/features/composites/structure-mode.js.map +1 -1
- package/dist/features/composites/table-model.js +2 -2
- package/dist/features/composites/table-model.js.map +1 -1
- package/dist/features/composites/use-composite-inspector.js +3 -1
- package/dist/features/composites/use-composite-inspector.js.map +1 -1
- package/dist/features/composites/use-composite-rows.d.ts +2 -2
- package/dist/features/composites/use-composite-rows.js.map +1 -1
- package/dist/features/composites/vue.js +1 -1
- package/dist/features/composites/vue.js.map +1 -1
- package/dist/features/image-studio/components/ImageStudioDialog.js +150 -87
- package/dist/features/image-studio/components/ImageStudioDialog.js.map +1 -1
- package/dist/features/image-studio/components/ImageStudioDialog.vue.d.ts +5 -1
- package/dist/features/image-studio/components/ImageStudioLibraryPanel.js +186 -0
- package/dist/features/image-studio/components/ImageStudioLibraryPanel.js.map +1 -0
- package/dist/features/image-studio/components/ImageStudioLibraryTile.js +134 -0
- package/dist/features/image-studio/components/ImageStudioLibraryTile.js.map +1 -0
- package/dist/features/image-studio/components/ImageStudioView.js.map +1 -1
- package/dist/features/image-studio/feature.d.ts +1 -1
- package/dist/features/image-studio/feature.js +1 -1
- package/dist/features/image-studio/feature.js.map +1 -1
- package/dist/features/image-studio/index.d.ts +3 -2
- package/dist/features/image-studio/index.js +2 -2
- package/dist/features/image-studio/library.d.ts +35 -0
- package/dist/features/image-studio/node-core.d.ts +3 -3
- package/dist/features/image-studio/node-core.js +1 -1
- package/dist/features/image-studio/node-core.js.map +1 -1
- package/dist/features/image-studio/node-store.d.ts +1 -1
- package/dist/features/image-studio/node-store.js +1 -1
- package/dist/features/image-studio/node-store.js.map +1 -1
- package/dist/features/image-studio/react/ImageStudioDialog.d.ts +1 -1
- package/dist/features/image-studio/react/ImageStudioDialog.js +46 -5
- package/dist/features/image-studio/react/ImageStudioDialog.js.map +1 -1
- package/dist/features/image-studio/react/ImageStudioLibraryPanel.js +141 -0
- package/dist/features/image-studio/react/ImageStudioLibraryPanel.js.map +1 -0
- package/dist/features/image-studio/react/ImageStudioLibraryTile.js +105 -0
- package/dist/features/image-studio/react/ImageStudioLibraryTile.js.map +1 -0
- package/dist/features/image-studio/react/ImageStudioView.js +1 -1
- package/dist/features/image-studio/react/ImageStudioView.js.map +1 -1
- package/dist/features/image-studio/react/store.d.ts +8 -3
- package/dist/features/image-studio/react/store.js.map +1 -1
- package/dist/features/image-studio/react.d.ts +4 -3
- package/dist/features/image-studio/react.js +3 -3
- package/dist/features/image-studio/react.js.map +1 -1
- package/dist/features/image-studio/schema.d.ts +1 -1
- package/dist/features/image-studio/schema.js +1 -1
- package/dist/features/image-studio/schema.js.map +1 -1
- package/dist/features/image-studio/state.d.ts +8 -3
- package/dist/features/image-studio/state.js +11 -3
- package/dist/features/image-studio/state.js.map +1 -1
- package/dist/features/image-studio/store.d.ts +8 -3
- package/dist/features/image-studio/vue.d.ts +4 -3
- package/dist/features/image-studio/vue.js +3 -3
- package/dist/features/image-studio/vue.js.map +1 -1
- package/dist/features/mentions/tag.js +2 -1
- package/dist/features/mentions/tag.js.map +1 -1
- package/dist/features/themes/components/SettingsAppearanceTab.js +1 -1
- package/dist/features/themes/components/SettingsAppearanceTab.js.map +1 -1
- package/dist/features/themes/feature.d.ts +1 -1
- package/dist/features/themes/feature.js.map +1 -1
- package/dist/features/themes/react/SettingsAppearanceTab.d.ts +1 -1
- package/dist/features/themes/react/SettingsAppearanceTab.js +1 -1
- package/dist/features/themes/react/SettingsAppearanceTab.js.map +1 -1
- package/dist/features/themes/theme-runtime.js +1 -1
- package/dist/features/themes/theme-runtime.js.map +1 -1
- package/dist/features/transcript/TranscriptSegmentView.js +1 -1
- package/dist/features/transcript/TranscriptSegmentView.js.map +1 -1
- package/dist/features/transcript/react/TranscriptSegmentView.js +1 -1
- package/dist/features/transcript/react/TranscriptSegmentView.js.map +1 -1
- package/dist/features/transcript/segment-node.js.map +1 -1
- package/dist/features/transfer/feature.js +1 -1
- package/dist/features/transfer/feature.js.map +1 -1
- package/dist/features/transfer/transforms.d.ts +4 -4
- package/dist/features/transfer/transforms.js +12 -12
- package/dist/features/transfer/transforms.js.map +1 -1
- package/dist/i18n/de/ai.js +4 -4
- package/dist/i18n/de/ai.js.map +1 -1
- package/dist/i18n/de/authors.js +1 -1
- package/dist/i18n/de/authors.js.map +1 -1
- package/dist/i18n/de/comments.js +1 -1
- package/dist/i18n/de/comments.js.map +1 -1
- package/dist/i18n/de/composites.js +62 -59
- package/dist/i18n/de/composites.js.map +1 -1
- package/dist/i18n/de/image-studio.js +11 -2
- package/dist/i18n/de/image-studio.js.map +1 -1
- package/dist/i18n/de/integrations.js +20 -20
- package/dist/i18n/de/integrations.js.map +1 -1
- package/dist/i18n/de/markdown.js +1 -1
- package/dist/i18n/de/markdown.js.map +1 -1
- package/dist/i18n/de/palette.js +1 -1
- package/dist/i18n/de/palette.js.map +1 -1
- package/dist/i18n/de/themes.js +3 -3
- package/dist/i18n/de/themes.js.map +1 -1
- package/dist/i18n/de/transfer.js +2 -2
- package/dist/i18n/de/transfer.js.map +1 -1
- package/dist/i18n/de/tree.js +6 -6
- package/dist/i18n/de/tree.js.map +1 -1
- package/dist/i18n/de.d.ts +110 -98
- package/dist/i18n/en/ai.js +2 -2
- package/dist/i18n/en/ai.js.map +1 -1
- package/dist/i18n/en/composites.js +64 -61
- package/dist/i18n/en/composites.js.map +1 -1
- package/dist/i18n/en/image-studio.js +9 -0
- package/dist/i18n/en/image-studio.js.map +1 -1
- package/dist/i18n/en/integrations.js +9 -9
- package/dist/i18n/en/integrations.js.map +1 -1
- package/dist/i18n/en/markdown.js +1 -1
- package/dist/i18n/en/markdown.js.map +1 -1
- package/dist/i18n/en/palette.js +1 -1
- package/dist/i18n/en/palette.js.map +1 -1
- package/dist/i18n/en/themes.js +4 -4
- package/dist/i18n/en/themes.js.map +1 -1
- package/dist/i18n/en/tree.js +5 -5
- package/dist/i18n/en/tree.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/integrations/composite-ai/bibliography-fetch.d.ts +6 -6
- package/dist/integrations/composite-ai/bibliography-fetch.js +19 -19
- package/dist/integrations/composite-ai/bibliography-fetch.js.map +1 -1
- package/dist/integrations/composite-ai/composite-fill.d.ts +4 -4
- package/dist/integrations/composite-ai/composite-fill.js +5 -5
- package/dist/integrations/composite-ai/composite-fill.js.map +1 -1
- package/dist/integrations/composite-ai/composite-summary.d.ts +2 -2
- package/dist/integrations/composite-ai/composite-summary.js +5 -5
- package/dist/integrations/composite-ai/composite-summary.js.map +1 -1
- package/dist/integrations/composite-ai/derive.d.ts +17 -17
- package/dist/integrations/composite-ai/derive.js +27 -26
- package/dist/integrations/composite-ai/derive.js.map +1 -1
- package/dist/integrations/composite-ai/index.d.ts +14 -15
- package/dist/integrations/composite-ai/index.js +98 -77
- package/dist/integrations/composite-ai/index.js.map +1 -1
- package/dist/integrations/composite-ai/outline-sanitize.d.ts +1 -1
- package/dist/integrations/composite-ai/outline-sanitize.js +1 -1
- package/dist/integrations/composite-ai/outline-sanitize.js.map +1 -1
- package/dist/integrations/composite-ai/rendition.d.ts +9 -9
- package/dist/integrations/composite-ai/rendition.js +27 -27
- package/dist/integrations/composite-ai/rendition.js.map +1 -1
- package/dist/integrations/composite-ai/split.d.ts +8 -8
- package/dist/integrations/composite-ai/split.js +12 -12
- package/dist/integrations/composite-ai/split.js.map +1 -1
- package/dist/integrations/composite-ai/transfer-transforms.d.ts +2 -2
- package/dist/integrations/composite-ai/transfer-transforms.js +11 -11
- package/dist/integrations/composite-ai/transfer-transforms.js.map +1 -1
- package/dist/integrations/composite-media/index.d.ts +4 -3
- package/dist/integrations/composite-media/index.js +20 -14
- package/dist/integrations/composite-media/index.js.map +1 -1
- package/dist/markdown/MarkdownSurface.vue.d.ts +7 -7
- package/dist/markdown/document.js +1 -1
- package/dist/markdown/document.js.map +1 -1
- package/dist/nuxt/module.d.ts +48 -24
- package/dist/nuxt/module.js.map +1 -1
- package/dist/nuxt/runtime/server/plugins/editor-server.js +3 -2
- package/dist/nuxt/runtime/server/plugins/editor-server.js.map +1 -1
- package/dist/presets/compose.d.ts +6 -6
- package/dist/presets/compose.js +7 -4
- package/dist/presets/compose.js.map +1 -1
- package/dist/react/AdvancedEditor.d.ts +2 -2
- package/dist/react/AdvancedEditor.js +1 -1
- package/dist/react/AdvancedEditor.js.map +1 -1
- package/dist/react/compose.d.ts +1 -1
- package/dist/react/compose.js +1 -1
- package/dist/react/compose.js.map +1 -1
- package/dist/react/composed-surface.d.ts +5 -5
- package/dist/react/composed-surface.js +2 -2
- package/dist/react/composed-surface.js.map +1 -1
- package/dist/server/ai.d.ts +7 -7
- package/dist/server/ai.js +1 -1
- package/dist/server/ai.js.map +1 -1
- package/dist/server/auth.d.ts +1 -1
- package/dist/server/collab-state.d.ts +1 -1
- package/dist/server/collab-state.js.map +1 -1
- package/dist/server/collab.d.ts +4 -4
- package/dist/server/collab.js +22 -17
- package/dist/server/collab.js.map +1 -1
- package/dist/server/handlers/ai.d.ts +9 -6
- package/dist/server/handlers/ai.js +13 -17
- package/dist/server/handlers/ai.js.map +1 -1
- package/dist/server/handlers/context.d.ts +4 -0
- package/dist/server/handlers/context.js.map +1 -1
- package/dist/server/handlers/editor.d.ts +33 -32
- package/dist/server/handlers/editor.js +13 -8
- package/dist/server/handlers/editor.js.map +1 -1
- package/dist/server/handlers/images.d.ts +1 -1
- package/dist/server/handlers/images.js +1 -1
- package/dist/server/handlers/images.js.map +1 -1
- package/dist/server/host.d.ts +4 -0
- package/dist/server/host.js +10 -2
- package/dist/server/host.js.map +1 -1
- package/dist/server/index.d.ts +3 -3
- package/dist/server/ports.d.ts +8 -8
- package/dist/server/ports.js.map +1 -1
- package/dist/settings/index.d.ts +20 -19
- package/dist/settings/index.js +25 -24
- package/dist/settings/index.js.map +1 -1
- package/dist/settings/migrate.d.ts +18 -0
- package/dist/settings/migrate.js +46 -0
- package/dist/settings/migrate.js.map +1 -0
- package/dist/settings/values.d.ts +12 -12
- package/dist/settings/values.js +11 -11
- package/dist/settings/values.js.map +1 -1
- package/dist/styles/composites.css +10 -10
- package/dist/tree/DocumentTree.js +12 -5
- package/dist/tree/DocumentTree.js.map +1 -1
- package/dist/tree/drop-rules.js +5 -5
- package/dist/tree/drop-rules.js.map +1 -1
- package/dist/tree/react/DocumentTree.js +14 -5
- package/dist/tree/react/DocumentTree.js.map +1 -1
- package/dist/tree/tree-host.js +5 -5
- package/dist/tree/tree-host.js.map +1 -1
- package/dist/tree/tree-items.js +34 -28
- package/dist/tree/tree-items.js.map +1 -1
- package/dist/tree/tree-model.js +16 -16
- package/dist/tree/tree-model.js.map +1 -1
- package/dist/tree/tree-rows.js +2 -2
- package/dist/tree/tree-rows.js.map +1 -1
- package/dist/tree/tree-view-model.d.ts +2 -2
- package/dist/tree/tree-view-model.js.map +1 -1
- package/dist/vue/AdvancedEditor.js +2 -2
- package/dist/vue/AdvancedEditor.js.map +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +10 -10
- package/dist/vue/SimpleEditor.vue.d.ts +4 -4
- package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
- package/dist/vue/compose.d.ts +1 -1
- package/dist/vue/compose.js +1 -1
- package/dist/vue/compose.js.map +1 -1
- package/package.json +1 -1
- package/dist/features/composites/derivation.d.ts +0 -62
- package/dist/features/composites/derivation.js +0 -102
- package/dist/features/composites/derivation.js.map +0 -1
- package/dist/features/composites/kinds.d.ts +0 -89
- package/dist/features/composites/kinds.js +0 -285
- package/dist/features/composites/kinds.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","names":[],"sources":["../../../src/features/image-studio/schema.ts"],"sourcesContent":["import { type AnyExtension, mergeAttributes, Node } from '@tiptap/core'\nimport { TEXT_ABILITIES } from '../../core/schema'\nimport { attrComment, mediaTokenizer } from '../media/schema'\nimport { EMPTY_IMAGE_STUDIO_STATE, type ImageStudioState, isEmptyImageStudioState, normalizeImageStudioState, selectedImage } from './state'\n\n// Vue-free and server-importable (`@ecsplico/editor/image-studio/schema`): the stored document may contain the node, so\n// the server schema has it. The client adds the node view and the dialog.\n\n/** What `insertImageStudio` takes: the description, chat and images live in `studio`, `src` follows its selected image. */\nexport interface ImageStudioAttrs {\n alt?: string\n studio?: ImageStudioState\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n imageStudio: {\n /** Inserts a Bild-Studio block at the selection. */\n insertImageStudio: (attrs?: ImageStudioAttrs) => ReturnType\n /** Replaces the studio state of the block at `pos` and mirrors the selected image into its `src`. */\n setImageStudioState: (pos: number, state: ImageStudioState) => ReturnType\n }\n }\n}\n\n/** The Bild-Studio node of the document schema. */\nexport const imageStudioSchemaExtensions = (): AnyExtension[] => [ImageStudioNode]\n\n/** The `src` a block shows for `studio`: the url of its selected image, `null` without one. */\nconst srcOf = (studio: ImageStudioState): string | null => selectedImage(studio)?.url ?? null\n\nfunction parseStudioJson(json: string | null): unknown {\n if (!json) return undefined\n try {\n return JSON.parse(json)\n }\n catch {\n return undefined\n }\n}\n\n/**\n * The Bild-Studio block: a picture whose description,
|
|
1
|
+
{"version":3,"file":"schema.js","names":[],"sources":["../../../src/features/image-studio/schema.ts"],"sourcesContent":["import { type AnyExtension, mergeAttributes, Node } from '@tiptap/core'\nimport { TEXT_ABILITIES } from '../../core/schema'\nimport { attrComment, mediaTokenizer } from '../media/schema'\nimport { EMPTY_IMAGE_STUDIO_STATE, type ImageStudioState, isEmptyImageStudioState, normalizeImageStudioState, selectedImage } from './state'\n\n// Vue-free and server-importable (`@ecsplico/editor/image-studio/schema`): the stored document may contain the node, so\n// the server schema has it. The client adds the node view and the dialog.\n\n/** What `insertImageStudio` takes: the description, chat and images live in `studio`, `src` follows its selected image. */\nexport interface ImageStudioAttrs {\n alt?: string\n studio?: ImageStudioState\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n imageStudio: {\n /** Inserts a Bild-Studio block at the selection. */\n insertImageStudio: (attrs?: ImageStudioAttrs) => ReturnType\n /** Replaces the studio state of the block at `pos` and mirrors the selected image into its `src`. */\n setImageStudioState: (pos: number, state: ImageStudioState) => ReturnType\n }\n }\n}\n\n/** The Bild-Studio node of the document schema. */\nexport const imageStudioSchemaExtensions = (): AnyExtension[] => [ImageStudioNode]\n\n/** The `src` a block shows for `studio`: the url of its selected image, `null` without one. */\nconst srcOf = (studio: ImageStudioState): string | null => selectedImage(studio)?.url ?? null\n\nfunction parseStudioJson(json: string | null): unknown {\n if (!json) return undefined\n try {\n return JSON.parse(json)\n }\n catch {\n return undefined\n }\n}\n\n/**\n * The Bild-Studio block: a picture whose description, prompt blocks, references, previous generations and chat live in\n * the `studio` attribute (`ImageStudioState`); `src` mirrors the selected image. HTML: `<img data-image-studio src alt\n * data-studio=\"{json}\">`. Markdown: `[Bild-Studio: alt](src) <!-- {\"studio\":{…}} -->` on its own line, the comment left\n * out while the studio is empty. The label is the literal `Bild-Studio` in every UI language (it is what parsing looks for).\n */\nexport const ImageStudioNode = Node.create({\n name: 'imageStudio',\n group: 'block',\n atom: true,\n draggable: true,\n editorBlock: true,\n // A picture block holds no text: nothing text-related applies inside it.\n abilities: { revokes: TEXT_ABILITIES },\n addAttributes: () => ({\n src: { default: null },\n alt: { default: '' },\n studio: {\n default: EMPTY_IMAGE_STUDIO_STATE,\n parseHTML: el => normalizeImageStudioState(parseStudioJson(el.getAttribute('data-studio'))),\n renderHTML: attrs => ({ 'data-studio': JSON.stringify(attrs.studio) }),\n },\n }),\n // Before the stock image rule, which also matches an `<img src>`.\n parseHTML: () => [{ tag: 'img[data-image-studio]', priority: 100 }],\n renderHTML: ({ HTMLAttributes }) => ['img', mergeAttributes(HTMLAttributes, { 'data-image-studio': '' })],\n markdownTokenizer: mediaTokenizer('imageStudio', 'Bild-Studio'),\n parseMarkdown: (token, helpers) => helpers.createNode('imageStudio', {\n src: token.href || null,\n alt: token.title ?? '',\n studio: normalizeImageStudioState(token.attrs?.studio),\n }),\n renderMarkdown: (node) => {\n const alt = typeof node.attrs?.alt === 'string' ? node.attrs.alt : ''\n const studio = normalizeImageStudioState(node.attrs?.studio)\n const label = alt ? `Bild-Studio: ${alt.replace(/[[\\]\\\\]/g, '\\\\$&')}` : 'Bild-Studio'\n const src = typeof node.attrs?.src === 'string' ? node.attrs.src : ''\n return `[${label}](${src})${attrComment({ studio: isEmptyImageStudioState(studio) ? undefined : studio })}`\n },\n addCommands() {\n return {\n insertImageStudio: attrs => ({ commands }) => {\n const studio = normalizeImageStudioState(attrs?.studio)\n return commands.insertContent({ type: this.name, attrs: { src: srcOf(studio), alt: attrs?.alt ?? '', studio } })\n },\n setImageStudioState: (pos, state) => ({ tr, dispatch }) => {\n const node = tr.doc.nodeAt(pos)\n if (node?.type.name !== this.name) return false\n const studio = normalizeImageStudioState(state)\n if (dispatch) tr.setNodeMarkup(pos, undefined, { ...node.attrs, studio, src: srcOf(studio) })\n return true\n },\n }\n },\n})\n"],"mappings":";;;;;;AA0BA,MAAa,oCAAoD,CAAC,eAAe;;AAGjF,MAAM,SAAS,WAA4C,cAAc,MAAM,CAAC,EAAE,OAAO;AAEzF,SAAS,gBAAgB,MAA8B;CACrD,IAAI,CAAC,MAAM,OAAO,KAAA;CAClB,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,QACM;EACJ;CACF;AACF;;;;;;;AAQA,MAAa,kBAAkB,KAAK,OAAO;CACzC,MAAM;CACN,OAAO;CACP,MAAM;CACN,WAAW;CACX,aAAa;CAEb,WAAW,EAAE,SAAS,eAAe;CACrC,sBAAsB;EACpB,KAAK,EAAE,SAAS,KAAK;EACrB,KAAK,EAAE,SAAS,GAAG;EACnB,QAAQ;GACN,SAAS;GACT,YAAW,OAAM,0BAA0B,gBAAgB,GAAG,aAAa,aAAa,CAAC,CAAC;GAC1F,aAAY,WAAU,EAAE,eAAe,KAAK,UAAU,MAAM,MAAM,EAAE;EACtE;CACF;CAEA,iBAAiB,CAAC;EAAE,KAAK;EAA0B,UAAU;CAAI,CAAC;CAClE,aAAa,EAAE,qBAAqB,CAAC,OAAO,gBAAgB,gBAAgB,EAAE,qBAAqB,GAAG,CAAC,CAAC;CACxG,mBAAmB,eAAe,eAAe,aAAa;CAC9D,gBAAgB,OAAO,YAAY,QAAQ,WAAW,eAAe;EACnE,KAAK,MAAM,QAAQ;EACnB,KAAK,MAAM,SAAS;EACpB,QAAQ,0BAA0B,MAAM,OAAO,MAAM;CACvD,CAAC;CACD,iBAAiB,SAAS;EACxB,MAAM,MAAM,OAAO,KAAK,OAAO,QAAQ,WAAW,KAAK,MAAM,MAAM;EACnE,MAAM,SAAS,0BAA0B,KAAK,OAAO,MAAM;EAG3D,OAAO,IAFO,MAAM,gBAAgB,IAAI,QAAQ,YAAY,MAAM,MAAM,cAEvD,IADL,OAAO,KAAK,OAAO,QAAQ,WAAW,KAAK,MAAM,MAAM,GAC1C,GAAG,YAAY,EAAE,QAAQ,wBAAwB,MAAM,IAAI,KAAA,IAAY,OAAO,CAAC;CAC1G;CACA,cAAc;EACZ,OAAO;GACL,oBAAmB,WAAU,EAAE,eAAe;IAC5C,MAAM,SAAS,0BAA0B,OAAO,MAAM;IACtD,OAAO,SAAS,cAAc;KAAE,MAAM,KAAK;KAAM,OAAO;MAAE,KAAK,MAAM,MAAM;MAAG,KAAK,OAAO,OAAO;MAAI;KAAO;IAAE,CAAC;GACjH;GACA,sBAAsB,KAAK,WAAW,EAAE,IAAI,eAAe;IACzD,MAAM,OAAO,GAAG,IAAI,OAAO,GAAG;IAC9B,IAAI,MAAM,KAAK,SAAS,KAAK,MAAM,OAAO;IAC1C,MAAM,SAAS,0BAA0B,KAAK;IAC9C,IAAI,UAAU,GAAG,cAAc,KAAK,KAAA,GAAW;KAAE,GAAG,KAAK;KAAO;KAAQ,KAAK,MAAM,MAAM;IAAE,CAAC;IAC5F,OAAO;GACT;EACF;CACF;AACF,CAAC"}
|
|
@@ -11,7 +11,7 @@ export interface ImageStudioTurn {
|
|
|
11
11
|
export interface ImageStudioState {
|
|
12
12
|
/** The text describing the image; '' shows the placeholder. */
|
|
13
13
|
description: string;
|
|
14
|
-
/** Active
|
|
14
|
+
/** Active prompt blocks, by host id. */
|
|
15
15
|
promptBlockIds: string[];
|
|
16
16
|
/** Uploaded or picked references. */
|
|
17
17
|
references: ImageStudioImage[];
|
|
@@ -27,7 +27,7 @@ export declare const EMPTY_IMAGE_STUDIO_STATE: ImageStudioState;
|
|
|
27
27
|
/** The image that is the block's image (a generation or a reference), if the selection still points at one. */
|
|
28
28
|
export declare function selectedImage(state: ImageStudioState): ImageStudioImage | undefined;
|
|
29
29
|
export declare function setDescription(state: ImageStudioState, description: string): ImageStudioState;
|
|
30
|
-
/** Adds the
|
|
30
|
+
/** Adds the prompt block `id` to the active ones, or removes it when it is active. */
|
|
31
31
|
export declare function togglePromptBlock(state: ImageStudioState, id: string): ImageStudioState;
|
|
32
32
|
/** Flips `active` of the reference or generation `id`; an unknown id changes nothing. */
|
|
33
33
|
export declare function toggleImage(state: ImageStudioState, id: string): ImageStudioState;
|
|
@@ -37,6 +37,11 @@ export declare function select(state: ImageStudioState, id: string | null): Imag
|
|
|
37
37
|
export declare function removeImage(state: ImageStudioState, id: string): ImageStudioState;
|
|
38
38
|
/** Appends a reference; one with the same id is replaced where it stands. */
|
|
39
39
|
export declare function addReference(state: ImageStudioState, image: ImageStudioImage): ImageStudioState;
|
|
40
|
+
/**
|
|
41
|
+
* Makes `image` the block's image: unchanged when it already is; an image the block holds (reference or generation) is
|
|
42
|
+
* only selected; any other is added as a reference first.
|
|
43
|
+
*/
|
|
44
|
+
export declare function adoptImage(state: ImageStudioState, image: ImageStudioImage): ImageStudioState;
|
|
40
45
|
/** A chat generate means "this is the picture now": appends the image and its turn (pointing at the image) and selects it. */
|
|
41
46
|
export declare function addGeneration(state: ImageStudioState, image: ImageStudioImage, turn: ImageStudioTurn): ImageStudioState;
|
|
42
47
|
/**
|
|
@@ -45,7 +50,7 @@ export declare function addGeneration(state: ImageStudioState, image: ImageStudi
|
|
|
45
50
|
* state comes back unchanged (as a copy).
|
|
46
51
|
*/
|
|
47
52
|
export declare function normalizeImageStudioState(value: unknown): ImageStudioState;
|
|
48
|
-
/** Whether `state` holds nothing: no description,
|
|
53
|
+
/** Whether `state` holds nothing: no description, prompt block, image, selection or chat turn. */
|
|
49
54
|
export declare function isEmptyImageStudioState(state: ImageStudioState): boolean;
|
|
50
55
|
//#endregion
|
|
51
56
|
export type { ImageStudioImage, ImageStudioPromptBlock };
|
|
@@ -29,7 +29,7 @@ function setDescription(state, description) {
|
|
|
29
29
|
description
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
-
/** Adds the
|
|
32
|
+
/** Adds the prompt block `id` to the active ones, or removes it when it is active. */
|
|
33
33
|
function togglePromptBlock(state, id) {
|
|
34
34
|
const active = state.promptBlockIds.includes(id);
|
|
35
35
|
return {
|
|
@@ -69,6 +69,14 @@ function addReference(state, image) {
|
|
|
69
69
|
references: known ? state.references.map((other) => other.id === image.id ? { ...image } : other) : [...state.references, { ...image }]
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Makes `image` the block's image: unchanged when it already is; an image the block holds (reference or generation) is
|
|
74
|
+
* only selected; any other is added as a reference first.
|
|
75
|
+
*/
|
|
76
|
+
function adoptImage(state, image) {
|
|
77
|
+
if (state.selected === image.id) return state;
|
|
78
|
+
return select(hasImage(state, image.id) ? state : addReference(state, image), image.id);
|
|
79
|
+
}
|
|
72
80
|
/** A chat generate means "this is the picture now": appends the image and its turn (pointing at the image) and selects it. */
|
|
73
81
|
function addGeneration(state, image, turn) {
|
|
74
82
|
return {
|
|
@@ -137,11 +145,11 @@ function normalizeImageStudioState(value) {
|
|
|
137
145
|
selected: selected !== null && hasImage(state, selected) ? selected : null
|
|
138
146
|
};
|
|
139
147
|
}
|
|
140
|
-
/** Whether `state` holds nothing: no description,
|
|
148
|
+
/** Whether `state` holds nothing: no description, prompt block, image, selection or chat turn. */
|
|
141
149
|
function isEmptyImageStudioState(state) {
|
|
142
150
|
return state.description === "" && state.promptBlockIds.length === 0 && state.references.length === 0 && state.generations.length === 0 && state.selected === null && state.turns.length === 0;
|
|
143
151
|
}
|
|
144
152
|
//#endregion
|
|
145
|
-
export { EMPTY_IMAGE_STUDIO_STATE, addGeneration, addReference, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock };
|
|
153
|
+
export { EMPTY_IMAGE_STUDIO_STATE, addGeneration, addReference, adoptImage, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock };
|
|
146
154
|
|
|
147
155
|
//# sourceMappingURL=state.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","names":[],"sources":["../../../src/features/image-studio/state.ts"],"sourcesContent":["/**\n * The Bild-Studio's state: what a block holds about its picture. Host-agnostic\n * JSON; image ids are opaque to the library (a host uses its own asset ids).\n */\n\nimport { z } from 'zod'\nimport type { ImageStudioImage, ImageStudioPromptBlock } from '../../core/backend-types'\n\n// `ImageStudioImage` and `ImageStudioPromptBlock` are wire types of `EditorBackend.images`, so they live in\n// `core/backend-types.ts` (core never imports a feature); this module is their feature-side entry.\nexport type { ImageStudioImage, ImageStudioPromptBlock }\n\n/** One chat turn: the instruction and the generation it produced (or the problem). */\nexport interface ImageStudioTurn {\n id: string\n instruction: string\n imageId: string | null\n createdAt: string\n problem?: string\n}\n\nexport interface ImageStudioState {\n /** The text describing the image; '' shows the placeholder. */\n description: string\n /** Active Prompt-Bausteine, by host id. */\n promptBlockIds: string[]\n /** Uploaded or picked references. */\n references: ImageStudioImage[]\n /** Previous generations; an active one goes into the next generation as a reference. */\n generations: ImageStudioImage[]\n /** Id of the generation (or reference) that is the block's image. */\n selected: string | null\n /** The chat log: instruction → generation. */\n turns: ImageStudioTurn[]\n}\n\n/** The state of a block that has no description, no images and no chat yet. */\nexport const EMPTY_IMAGE_STUDIO_STATE: ImageStudioState = {\n description: '',\n promptBlockIds: [],\n references: [],\n generations: [],\n selected: null,\n turns: [],\n}\n\n// Reducers: pure, each returns a new state and never touches its argument. The host owns the files behind the images.\n\nconst hasImage = (state: ImageStudioState, id: string): boolean =>\n state.references.some(image => image.id === id) || state.generations.some(image => image.id === id)\n\nconst toggled = (images: readonly ImageStudioImage[], id: string): ImageStudioImage[] =>\n images.map(image => ({ ...image, active: image.id === id ? !image.active : image.active }))\n\n/** The image that is the block's image (a generation or a reference), if the selection still points at one. */\nexport function selectedImage(state: ImageStudioState): ImageStudioImage | undefined {\n if (state.selected === null) return undefined\n return state.generations.find(image => image.id === state.selected) ?? state.references.find(image => image.id === state.selected)\n}\n\nexport function setDescription(state: ImageStudioState, description: string): ImageStudioState {\n return { ...state, description }\n}\n\n/** Adds the Prompt-Baustein `id` to the active ones, or removes it when it is active. */\nexport function togglePromptBlock(state: ImageStudioState, id: string): ImageStudioState {\n const active = state.promptBlockIds.includes(id)\n return { ...state, promptBlockIds: active ? state.promptBlockIds.filter(other => other !== id) : [...state.promptBlockIds, id] }\n}\n\n/** Flips `active` of the reference or generation `id`; an unknown id changes nothing. */\nexport function toggleImage(state: ImageStudioState, id: string): ImageStudioState {\n return { ...state, references: toggled(state.references, id), generations: toggled(state.generations, id) }\n}\n\n/** Makes the reference or generation `id` the block's image; `null` clears the choice. An unknown id changes nothing. */\nexport function select(state: ImageStudioState, id: string | null): ImageStudioState {\n return { ...state, selected: id === null || hasImage(state, id) ? id : state.selected }\n}\n\n/** Drops the image `id` from the block (references and generations); the selection goes with it, the chat log keeps its turns. */\nexport function removeImage(state: ImageStudioState, id: string): ImageStudioState {\n return {\n ...state,\n references: state.references.filter(image => image.id !== id),\n generations: state.generations.filter(image => image.id !== id),\n selected: state.selected === id ? null : state.selected,\n }\n}\n\n/** Appends a reference; one with the same id is replaced where it stands. */\nexport function addReference(state: ImageStudioState, image: ImageStudioImage): ImageStudioState {\n const known = state.references.some(other => other.id === image.id)\n return { ...state, references: known ? state.references.map(other => other.id === image.id ? { ...image } : other) : [...state.references, { ...image }] }\n}\n\n/** A chat generate means \"this is the picture now\": appends the image and its turn (pointing at the image) and selects it. */\nexport function addGeneration(state: ImageStudioState, image: ImageStudioImage, turn: ImageStudioTurn): ImageStudioState {\n return {\n ...state,\n generations: [...state.generations, { ...image }],\n turns: [...state.turns, { ...turn, imageId: image.id }],\n selected: image.id,\n }\n}\n\n// Parsing: stored attributes and JSON from other tools are untrusted, a missing or mistyped field falls back to its empty value.\n// Fields are read one by one from a record: a zod object would reject an entry for a missing key, where this one defaults it.\nconst Fields = z.record(z.string(), z.unknown())\n\nconst textOf = (value: unknown): string => typeof value === 'string' ? value : ''\nconst idOf = (value: unknown): string | null => typeof value === 'string' && value !== '' ? value : null\n\n/** The entries of the array `value` that `parse` accepts (it answers `null` for a refused one); a non-array has none. */\nfunction entriesOf<T>(value: unknown, parse: (entry: unknown) => T | null): T[] {\n return Array.isArray(value) ? value.flatMap((entry: unknown) => parse(entry) ?? []) : []\n}\n\nfunction imageOf(entry: unknown): ImageStudioImage | null {\n const fields = Fields.safeParse(entry)\n const id = fields.success ? idOf(fields.data.id) : null\n if (!fields.success || id === null) return null\n return { id, url: textOf(fields.data.url), title: textOf(fields.data.title), active: fields.data.active === true }\n}\n\nfunction turnOf(entry: unknown): ImageStudioTurn | null {\n const fields = Fields.safeParse(entry)\n const id = fields.success ? idOf(fields.data.id) : null\n if (!fields.success || id === null) return null\n const turn: ImageStudioTurn = { id, instruction: textOf(fields.data.instruction), imageId: idOf(fields.data.imageId), createdAt: textOf(fields.data.createdAt) }\n const problem = idOf(fields.data.problem)\n return problem === null ? turn : { ...turn, problem }\n}\n\n/**\n * The state `value` stands for, for parsed attributes and stored JSON: a missing field, a wrong type or an entry without\n * an id falls back to the empty value of that field, and a `selected` that points at no image becomes `null`. A valid\n * state comes back unchanged (as a copy).\n */\nexport function normalizeImageStudioState(value: unknown): ImageStudioState {\n const fields = Fields.safeParse(value)\n const data = fields.success ? fields.data : {}\n const state: ImageStudioState = {\n description: textOf(data.description),\n promptBlockIds: entriesOf(data.promptBlockIds, idOf),\n references: entriesOf(data.references, imageOf),\n generations: entriesOf(data.generations, imageOf),\n selected: null,\n turns: entriesOf(data.turns, turnOf),\n }\n const selected = idOf(data.selected)\n return { ...state, selected: selected !== null && hasImage(state, selected) ? selected : null }\n}\n\n/** Whether `state` holds nothing: no description, Prompt-Baustein, image, selection or chat turn. */\nexport function isEmptyImageStudioState(state: ImageStudioState): boolean {\n return state.description === '' && state.promptBlockIds.length === 0 && state.references.length === 0 && state.generations.length === 0\n && state.selected === null && state.turns.length === 0\n}\n\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"state.js","names":[],"sources":["../../../src/features/image-studio/state.ts"],"sourcesContent":["/**\n * The Bild-Studio's state: what a block holds about its picture. Host-agnostic\n * JSON; image ids are opaque to the library (a host uses its own asset ids).\n */\n\nimport { z } from 'zod'\nimport type { ImageStudioImage, ImageStudioPromptBlock } from '../../core/backend-types'\n\n// `ImageStudioImage` and `ImageStudioPromptBlock` are wire types of `EditorBackend.images`, so they live in\n// `core/backend-types.ts` (core never imports a feature); this module is their feature-side entry.\nexport type { ImageStudioImage, ImageStudioPromptBlock }\n\n/** One chat turn: the instruction and the generation it produced (or the problem). */\nexport interface ImageStudioTurn {\n id: string\n instruction: string\n imageId: string | null\n createdAt: string\n problem?: string\n}\n\nexport interface ImageStudioState {\n /** The text describing the image; '' shows the placeholder. */\n description: string\n /** Active prompt blocks, by host id. */\n promptBlockIds: string[]\n /** Uploaded or picked references. */\n references: ImageStudioImage[]\n /** Previous generations; an active one goes into the next generation as a reference. */\n generations: ImageStudioImage[]\n /** Id of the generation (or reference) that is the block's image. */\n selected: string | null\n /** The chat log: instruction → generation. */\n turns: ImageStudioTurn[]\n}\n\n/** The state of a block that has no description, no images and no chat yet. */\nexport const EMPTY_IMAGE_STUDIO_STATE: ImageStudioState = {\n description: '',\n promptBlockIds: [],\n references: [],\n generations: [],\n selected: null,\n turns: [],\n}\n\n// Reducers: pure, each returns a new state and never touches its argument. The host owns the files behind the images.\n\nconst hasImage = (state: ImageStudioState, id: string): boolean =>\n state.references.some(image => image.id === id) || state.generations.some(image => image.id === id)\n\nconst toggled = (images: readonly ImageStudioImage[], id: string): ImageStudioImage[] =>\n images.map(image => ({ ...image, active: image.id === id ? !image.active : image.active }))\n\n/** The image that is the block's image (a generation or a reference), if the selection still points at one. */\nexport function selectedImage(state: ImageStudioState): ImageStudioImage | undefined {\n if (state.selected === null) return undefined\n return state.generations.find(image => image.id === state.selected) ?? state.references.find(image => image.id === state.selected)\n}\n\nexport function setDescription(state: ImageStudioState, description: string): ImageStudioState {\n return { ...state, description }\n}\n\n/** Adds the prompt block `id` to the active ones, or removes it when it is active. */\nexport function togglePromptBlock(state: ImageStudioState, id: string): ImageStudioState {\n const active = state.promptBlockIds.includes(id)\n return { ...state, promptBlockIds: active ? state.promptBlockIds.filter(other => other !== id) : [...state.promptBlockIds, id] }\n}\n\n/** Flips `active` of the reference or generation `id`; an unknown id changes nothing. */\nexport function toggleImage(state: ImageStudioState, id: string): ImageStudioState {\n return { ...state, references: toggled(state.references, id), generations: toggled(state.generations, id) }\n}\n\n/** Makes the reference or generation `id` the block's image; `null` clears the choice. An unknown id changes nothing. */\nexport function select(state: ImageStudioState, id: string | null): ImageStudioState {\n return { ...state, selected: id === null || hasImage(state, id) ? id : state.selected }\n}\n\n/** Drops the image `id` from the block (references and generations); the selection goes with it, the chat log keeps its turns. */\nexport function removeImage(state: ImageStudioState, id: string): ImageStudioState {\n return {\n ...state,\n references: state.references.filter(image => image.id !== id),\n generations: state.generations.filter(image => image.id !== id),\n selected: state.selected === id ? null : state.selected,\n }\n}\n\n/** Appends a reference; one with the same id is replaced where it stands. */\nexport function addReference(state: ImageStudioState, image: ImageStudioImage): ImageStudioState {\n const known = state.references.some(other => other.id === image.id)\n return { ...state, references: known ? state.references.map(other => other.id === image.id ? { ...image } : other) : [...state.references, { ...image }] }\n}\n\n/**\n * Makes `image` the block's image: unchanged when it already is; an image the block holds (reference or generation) is\n * only selected; any other is added as a reference first.\n */\nexport function adoptImage(state: ImageStudioState, image: ImageStudioImage): ImageStudioState {\n if (state.selected === image.id) return state\n return select(hasImage(state, image.id) ? state : addReference(state, image), image.id)\n}\n\n/** A chat generate means \"this is the picture now\": appends the image and its turn (pointing at the image) and selects it. */\nexport function addGeneration(state: ImageStudioState, image: ImageStudioImage, turn: ImageStudioTurn): ImageStudioState {\n return {\n ...state,\n generations: [...state.generations, { ...image }],\n turns: [...state.turns, { ...turn, imageId: image.id }],\n selected: image.id,\n }\n}\n\n// Parsing: stored attributes and JSON from other tools are untrusted, a missing or mistyped field falls back to its empty value.\n// Fields are read one by one from a record: a zod object would reject an entry for a missing key, where this one defaults it.\nconst Fields = z.record(z.string(), z.unknown())\n\nconst textOf = (value: unknown): string => typeof value === 'string' ? value : ''\nconst idOf = (value: unknown): string | null => typeof value === 'string' && value !== '' ? value : null\n\n/** The entries of the array `value` that `parse` accepts (it answers `null` for a refused one); a non-array has none. */\nfunction entriesOf<T>(value: unknown, parse: (entry: unknown) => T | null): T[] {\n return Array.isArray(value) ? value.flatMap((entry: unknown) => parse(entry) ?? []) : []\n}\n\nfunction imageOf(entry: unknown): ImageStudioImage | null {\n const fields = Fields.safeParse(entry)\n const id = fields.success ? idOf(fields.data.id) : null\n if (!fields.success || id === null) return null\n return { id, url: textOf(fields.data.url), title: textOf(fields.data.title), active: fields.data.active === true }\n}\n\nfunction turnOf(entry: unknown): ImageStudioTurn | null {\n const fields = Fields.safeParse(entry)\n const id = fields.success ? idOf(fields.data.id) : null\n if (!fields.success || id === null) return null\n const turn: ImageStudioTurn = { id, instruction: textOf(fields.data.instruction), imageId: idOf(fields.data.imageId), createdAt: textOf(fields.data.createdAt) }\n const problem = idOf(fields.data.problem)\n return problem === null ? turn : { ...turn, problem }\n}\n\n/**\n * The state `value` stands for, for parsed attributes and stored JSON: a missing field, a wrong type or an entry without\n * an id falls back to the empty value of that field, and a `selected` that points at no image becomes `null`. A valid\n * state comes back unchanged (as a copy).\n */\nexport function normalizeImageStudioState(value: unknown): ImageStudioState {\n const fields = Fields.safeParse(value)\n const data = fields.success ? fields.data : {}\n const state: ImageStudioState = {\n description: textOf(data.description),\n promptBlockIds: entriesOf(data.promptBlockIds, idOf),\n references: entriesOf(data.references, imageOf),\n generations: entriesOf(data.generations, imageOf),\n selected: null,\n turns: entriesOf(data.turns, turnOf),\n }\n const selected = idOf(data.selected)\n return { ...state, selected: selected !== null && hasImage(state, selected) ? selected : null }\n}\n\n/** Whether `state` holds nothing: no description, prompt block, image, selection or chat turn. */\nexport function isEmptyImageStudioState(state: ImageStudioState): boolean {\n return state.description === '' && state.promptBlockIds.length === 0 && state.references.length === 0 && state.generations.length === 0\n && state.selected === null && state.turns.length === 0\n}\n\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ImageStudioImage, ImageStudioPromptBlock } from "../../core/backend-types.js";
|
|
2
2
|
import { ImageStudioState } from "./state.js";
|
|
3
|
+
import { ImageStudioLibrary } from "./library.js";
|
|
3
4
|
import { Ref } from "vue";
|
|
4
5
|
//#region src/features/image-studio/store.d.ts
|
|
5
6
|
/**
|
|
@@ -8,22 +9,26 @@ import { Ref } from "vue";
|
|
|
8
9
|
*
|
|
9
10
|
* The dialog applies the reducers of `./state` and hands the result to `update`; the store only persists it. The
|
|
10
11
|
* store's methods never touch `state` themselves:
|
|
11
|
-
* - `generate` produces one image from the block's `state.description`, the active
|
|
12
|
+
* - `generate` produces one image from the block's `state.description`, the active prompt blocks and the active
|
|
12
13
|
* images (the host builds the prompt). When it resolves, the dialog calls `update(addGeneration(state, image, turn))`;
|
|
13
14
|
* when it rejects (and the signal was not aborted) the dialog appends a turn with `imageId: null` and the message as
|
|
14
15
|
* `problem`. An aborted generation adds nothing.
|
|
15
16
|
* - `upload` stores the file and returns the image; the dialog then calls `update(addReference(state, image))`.
|
|
16
|
-
* - `problem` is a problem of the store itself (the
|
|
17
|
+
* - `problem` is a problem of the store itself (the prompt blocks could not be loaded, say); the dialog shows it as a notice.
|
|
17
18
|
*/
|
|
18
19
|
export interface ImageStudioStore {
|
|
19
20
|
readonly state: Ref<ImageStudioState>;
|
|
20
|
-
/** The managed list of
|
|
21
|
+
/** The managed list of prompt blocks, loaded by the host. */
|
|
21
22
|
readonly promptBlocks: Ref<readonly ImageStudioPromptBlock[]>;
|
|
22
23
|
/** Called with the state after a reducer; the host persists it. */
|
|
23
24
|
update: (next: ImageStudioState) => void;
|
|
24
25
|
generate: (instruction: string, signal: AbortSignal) => Promise<ImageStudioImage>;
|
|
25
26
|
upload: (file: File) => Promise<ImageStudioImage>;
|
|
26
27
|
readonly problem: Ref<string | undefined>;
|
|
28
|
+
/** The host's gallery; the dialog shows a «Galerie» tab only when it is set. */
|
|
29
|
+
readonly library?: ImageStudioLibrary;
|
|
30
|
+
/** The URL of the image shown in the preview while no image is selected. */
|
|
31
|
+
readonly placeholderUrl?: string;
|
|
27
32
|
}
|
|
28
33
|
//#endregion
|
|
29
34
|
//# sourceMappingURL=store.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ImageStudioImage, ImageStudioPromptBlock } from "../../core/backend-types.js";
|
|
2
2
|
import { VueFeature } from "../../vue/index.js";
|
|
3
|
-
import { EMPTY_IMAGE_STUDIO_STATE, ImageStudioState, ImageStudioTurn, addGeneration, addReference, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock } from "./state.js";
|
|
3
|
+
import { EMPTY_IMAGE_STUDIO_STATE, ImageStudioState, ImageStudioTurn, addGeneration, addReference, adoptImage, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock } from "./state.js";
|
|
4
|
+
import { ImageStudioLibrary, ImageStudioLibraryFacet, ImageStudioLibraryItem, ImageStudioLibraryQuery } from "./library.js";
|
|
4
5
|
import { ImageStudioStore } from "./store.js";
|
|
5
6
|
import _default from "./components/ImageStudioDialog.vue.js";
|
|
6
7
|
import { ImageStudioNodeStore, ImageStudioNodeStoreOptions, createImageStudioNodeStore } from "./node-store.js";
|
|
@@ -8,10 +9,10 @@ import { ImageStudioNodeStore, ImageStudioNodeStoreOptions, createImageStudioNod
|
|
|
8
9
|
/**
|
|
9
10
|
* The Vue Bild-Studio feature: the `imageStudio` block with its Vue view (picture or description placeholder, opens the
|
|
10
11
|
* studio dialog on double-click and through the block action `image-studio.open`), the `/` entry `bild-studio` and the
|
|
11
|
-
* action. Generation, upload and the
|
|
12
|
+
* action. Generation, upload and the prompt blocks need a backend with the `images` capability; without it the view
|
|
12
13
|
* says that generation is not set up.
|
|
13
14
|
*/
|
|
14
15
|
export declare function imageStudioFeature(): VueFeature;
|
|
15
16
|
//#endregion
|
|
16
|
-
export { EMPTY_IMAGE_STUDIO_STATE, _default as ImageStudioDialog, type ImageStudioImage, type ImageStudioNodeStore, type ImageStudioNodeStoreOptions, type ImageStudioPromptBlock, type ImageStudioState, type ImageStudioStore, type ImageStudioTurn, addGeneration, addReference, createImageStudioNodeStore, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock };
|
|
17
|
+
export { EMPTY_IMAGE_STUDIO_STATE, _default as ImageStudioDialog, type ImageStudioImage, type ImageStudioLibrary, type ImageStudioLibraryFacet, type ImageStudioLibraryItem, type ImageStudioLibraryQuery, type ImageStudioNodeStore, type ImageStudioNodeStoreOptions, type ImageStudioPromptBlock, type ImageStudioState, type ImageStudioStore, type ImageStudioTurn, addGeneration, addReference, adoptImage, createImageStudioNodeStore, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock };
|
|
17
18
|
//# sourceMappingURL=vue.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EMPTY_IMAGE_STUDIO_STATE, addGeneration, addReference, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock } from "./state.js";
|
|
1
|
+
import { EMPTY_IMAGE_STUDIO_STATE, addGeneration, addReference, adoptImage, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock } from "./state.js";
|
|
2
2
|
import { imageStudioFeatureCore } from "./feature.js";
|
|
3
3
|
import { createImageStudioNodeStore } from "./node-store.js";
|
|
4
4
|
import _sfc_main from "./components/ImageStudioDialog.js";
|
|
@@ -8,13 +8,13 @@ import { VueNodeViewRenderer } from "@tiptap/vue-3";
|
|
|
8
8
|
/**
|
|
9
9
|
* The Vue Bild-Studio feature: the `imageStudio` block with its Vue view (picture or description placeholder, opens the
|
|
10
10
|
* studio dialog on double-click and through the block action `image-studio.open`), the `/` entry `bild-studio` and the
|
|
11
|
-
* action. Generation, upload and the
|
|
11
|
+
* action. Generation, upload and the prompt blocks need a backend with the `images` capability; without it the view
|
|
12
12
|
* says that generation is not set up.
|
|
13
13
|
*/
|
|
14
14
|
function imageStudioFeature() {
|
|
15
15
|
return imageStudioFeatureCore({ nodeView: VueNodeViewRenderer(_sfc_main$1) });
|
|
16
16
|
}
|
|
17
17
|
//#endregion
|
|
18
|
-
export { EMPTY_IMAGE_STUDIO_STATE, _sfc_main as ImageStudioDialog, addGeneration, addReference, createImageStudioNodeStore, imageStudioFeature, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock };
|
|
18
|
+
export { EMPTY_IMAGE_STUDIO_STATE, _sfc_main as ImageStudioDialog, addGeneration, addReference, adoptImage, createImageStudioNodeStore, imageStudioFeature, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock };
|
|
19
19
|
|
|
20
20
|
//# sourceMappingURL=vue.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vue.js","names":["ImageStudioView"],"sources":["../../../src/features/image-studio/vue.ts"],"sourcesContent":["import { VueNodeViewRenderer } from '@tiptap/vue-3'\nimport type { VueFeature } from '../../vue'\nimport ImageStudioView from './components/ImageStudioView.vue'\nimport { imageStudioFeatureCore } from './feature'\n\nexport { default as ImageStudioDialog } from './components/ImageStudioDialog.vue'\nexport { createImageStudioNodeStore, type ImageStudioNodeStore, type ImageStudioNodeStoreOptions } from './node-store'\nexport {\n addGeneration,\n addReference,\n EMPTY_IMAGE_STUDIO_STATE,\n type ImageStudioImage,\n type ImageStudioPromptBlock,\n type ImageStudioState,\n type ImageStudioTurn,\n isEmptyImageStudioState,\n normalizeImageStudioState,\n removeImage,\n select,\n selectedImage,\n setDescription,\n toggleImage,\n togglePromptBlock,\n} from './state'\nexport type { ImageStudioStore } from './store'\n\n/**\n * The Vue Bild-Studio feature: the `imageStudio` block with its Vue view (picture or description placeholder, opens the\n * studio dialog on double-click and through the block action `image-studio.open`), the `/` entry `bild-studio` and the\n * action. Generation, upload and the
|
|
1
|
+
{"version":3,"file":"vue.js","names":["ImageStudioView"],"sources":["../../../src/features/image-studio/vue.ts"],"sourcesContent":["import { VueNodeViewRenderer } from '@tiptap/vue-3'\nimport type { VueFeature } from '../../vue'\nimport ImageStudioView from './components/ImageStudioView.vue'\nimport { imageStudioFeatureCore } from './feature'\n\nexport { default as ImageStudioDialog } from './components/ImageStudioDialog.vue'\nexport { createImageStudioNodeStore, type ImageStudioNodeStore, type ImageStudioNodeStoreOptions } from './node-store'\nexport {\n addGeneration,\n addReference,\n adoptImage,\n EMPTY_IMAGE_STUDIO_STATE,\n type ImageStudioImage,\n type ImageStudioPromptBlock,\n type ImageStudioState,\n type ImageStudioTurn,\n isEmptyImageStudioState,\n normalizeImageStudioState,\n removeImage,\n select,\n selectedImage,\n setDescription,\n toggleImage,\n togglePromptBlock,\n} from './state'\nexport type { ImageStudioLibrary, ImageStudioLibraryFacet, ImageStudioLibraryItem, ImageStudioLibraryQuery } from './library'\nexport type { ImageStudioStore } from './store'\n\n/**\n * The Vue Bild-Studio feature: the `imageStudio` block with its Vue view (picture or description placeholder, opens the\n * studio dialog on double-click and through the block action `image-studio.open`), the `/` entry `bild-studio` and the\n * action. Generation, upload and the prompt blocks need a backend with the `images` capability; without it the view\n * says that generation is not set up.\n */\nexport function imageStudioFeature(): VueFeature {\n return imageStudioFeatureCore({ nodeView: VueNodeViewRenderer(ImageStudioView) })\n}\n"],"mappings":";;;;;;;;;;;;;AAkCA,SAAgB,qBAAiC;CAC/C,OAAO,uBAAuB,EAAE,UAAU,oBAAoBA,WAAe,EAAE,CAAC;AAClF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "../../core/schema/persisted-names.js";
|
|
1
2
|
import { Node, mergeAttributes } from "@tiptap/core";
|
|
2
3
|
import { PluginKey } from "@tiptap/pm/state";
|
|
3
4
|
import { Suggestion } from "@tiptap/suggestion";
|
|
@@ -15,7 +16,7 @@ function normalizeTagName(raw) {
|
|
|
15
16
|
function collectTagNames(content, into) {
|
|
16
17
|
if (!content) return into;
|
|
17
18
|
if (content.type === "tag" && typeof content.attrs?.name === "string" && content.attrs.name) into.add(content.attrs.name);
|
|
18
|
-
if (content.type === "
|
|
19
|
+
if (content.type === "typedBlockTags" && Array.isArray(content.attrs?.tags)) {
|
|
19
20
|
for (const name of content.attrs.tags) if (typeof name === "string" && name) into.add(name);
|
|
20
21
|
}
|
|
21
22
|
for (const child of content.content ?? []) collectTagNames(child, into);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tag.js","names":[],"sources":["../../../src/features/mentions/tag.ts"],"sourcesContent":["import type { Editor, JSONContent } from '@tiptap/core'\nimport { mergeAttributes, Node } from '@tiptap/core'\nimport { PluginKey } from '@tiptap/pm/state'\nimport { Suggestion, type SuggestionOptions } from '@tiptap/suggestion'\n\n/** Attributes of a tag chip; `name` is always normalised (see `normalizeTagName`). */\nexport type TagAttrs = { name: string }\n\n/** A `#` popup row: an existing workspace tag, or (`isNew`) the offer to create the typed one. Picking passes it on as `TagAttrs`. */\nexport type TagSuggestionItem = TagAttrs & { isNew: boolean }\n\nexport const TAG_NODE = 'tag'\nconst MAX_TAG_LENGTH = 64\n\n/**\n * Canonical tag name: lowercase, trimmed, whitespace runs → `-`, only letters (incl. umlauts/ß), digits, `-` and `_`,\n * repeated/edge dashes collapsed. `' Neu Tag! '` → `'neu-tag'`; `''` means \"no usable name\".\n */\nexport function normalizeTagName(raw: string): string {\n return raw.trim().toLowerCase()\n .replace(/\\s+/g, '-')\n .replace(/[^\\p{L}\\p{N}_-]/gu, '')\n .replace(/-{2,}/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, MAX_TAG_LENGTH)\n}\n\n/** Adds every tag name found in a TipTap JSON tree to `into`: tag chips and the tags rows of composites. */\nexport function collectTagNames(content: JSONContent | null | undefined, into: Set<string>): Set<string> {\n if (!content) return into\n if (content.type === TAG_NODE && typeof content.attrs?.name === 'string' && content.attrs.name) into.add(content.attrs.name)\n if (content.type === 'compositeTags' && Array.isArray(content.attrs?.tags)) {\n for (const name of content.attrs.tags) if (typeof name === 'string' && name) into.add(name)\n }\n for (const child of content.content ?? []) collectTagNames(child, into)\n return into\n}\n\n/**\n * Turns `#name` tokens in text nodes into tag chips — for AI-generated Markdown, where chips come back as their\n * Markdown form `#name` (see renderMarkdown). Only `names` (normalised tag names, e.g. the tags already in the\n * document) match, case-insensitively; other `#words`, `C#` and text in code blocks, inline code or links stay plain.\n * Returns the input unchanged when nothing matched.\n */\nexport function tagsFromText(json: JSONContent, names: ReadonlySet<string>): JSONContent {\n if (!names.size) return json\n // Not preceded by a word char, `#`, `&` or `/` (URL fragments, entities); the whole name up to the next non-name char.\n const token = /(?<![\\p{L}\\p{N}_#&/])#([\\p{L}\\p{N}_-]+)/gu\n\n const splitText = (node: JSONContent): JSONContent[] => {\n const text = node.text ?? ''\n if (!text.includes('#') || node.marks?.some(m => m.type === 'code' || m.type === 'link')) return [node]\n const out: JSONContent[] = []\n let last = 0\n for (const match of text.matchAll(token)) {\n const name = match[1]!.toLowerCase()\n if (!names.has(name)) continue\n const at = match.index ?? 0\n if (at > last) out.push({ ...node, text: text.slice(last, at) })\n out.push({ type: TAG_NODE, attrs: { name }, ...(node.marks?.length ? { marks: node.marks } : {}) })\n last = at + match[0].length\n }\n if (!out.length) return [node]\n if (last < text.length) out.push({ ...node, text: text.slice(last) })\n return out\n }\n\n const walk = (node: JSONContent): JSONContent => {\n if (!node.content || node.type === 'codeBlock') return node\n let changed = false\n const content = node.content.flatMap((child) => {\n const next = child.type === 'text' ? splitText(child) : [walk(child)]\n if (next.length !== 1 || next[0] !== child) changed = true\n return next\n })\n return changed ? { ...node, content } : node\n }\n return walk(json)\n}\n\nexport interface TagOptions {\n HTMLAttributes: Record<string, unknown>\n /** `#` popup (client only, see tag-suggestion.ts); `null` = schema only (server, Markdown). */\n suggestion: Omit<SuggestionOptions<TagSuggestionItem, TagAttrs>, 'editor'> | null\n /** Called after a chip for a newly created tag (`isNew` row) was inserted, e.g. to register it in the metadata. */\n onCreate: ((editor: Editor, name: string) => void) | null\n}\n\n/** Own key, so the `#` popup runs next to the `@` mention popup (Mention uses the default suggestion key). */\nexport const TagSuggestionPluginKey = new PluginKey('tagSuggestion')\n\n/**\n * Inline `#name` chip, shared by the client editor and the server (collab schema, Markdown).\n * The client adds the `#` popup via `.configure({ suggestion: tagSuggestion })` in the mentions feature.\n */\nexport const TagNode = Node.create<TagOptions>({\n name: TAG_NODE,\n group: 'inline',\n inline: true,\n atom: true,\n selectable: false,\n // Above paragraph-level parse rules, like Mention.\n priority: 101,\n\n addOptions() {\n return { HTMLAttributes: { class: 'tag' }, suggestion: null, onCreate: null }\n },\n\n addAttributes() {\n return {\n name: {\n default: '',\n parseHTML: el => normalizeTagName(el.getAttribute('data-name') ?? el.textContent ?? ''),\n renderHTML: attrs => ({ 'data-name': attrs.name }),\n },\n }\n },\n\n parseHTML() {\n return [{ tag: `span[data-type=\"${this.name}\"]` }]\n },\n\n renderHTML({ node, HTMLAttributes }) {\n return ['span', mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes), `#${node.attrs.name}`]\n },\n\n renderText: ({ node }) => `#${node.attrs.name}`,\n\n // Markdown export (editor.getMarkdown(), AI context/selection) shows the chip as plain `#name`.\n renderMarkdown: node => `#${node.attrs?.name ?? ''}`,\n\n addKeyboardShortcuts() {\n return {\n // Backspace right after a chip turns it back into editable `#name` text (which reopens the popup).\n Backspace: () => this.editor.commands.command(({ tr, state }) => {\n const { empty, $anchor } = state.selection\n const before = $anchor.nodeBefore\n if (!empty || before?.type.name !== this.name) return false\n tr.insertText(`#${before.attrs.name}`, $anchor.pos - before.nodeSize, $anchor.pos)\n return true\n }),\n }\n },\n\n addProseMirrorPlugins() {\n const { suggestion } = this.options\n if (!suggestion) return []\n const { allow, ...rest } = suggestion\n // `# `/`## ` at line start stay heading input rules, and `#` inside words (`C#`) never opens the popup.\n const builtInAllow: NonNullable<typeof allow> = ({ state, range }) => !/^[\\s#]/.test(state.doc.textBetween(range.from + 1, range.to))\n return [Suggestion<TagSuggestionItem, TagAttrs>({\n pluginKey: TagSuggestionPluginKey,\n char: '#',\n allowSpaces: true,\n allow: p => builtInAllow(p) && (allow?.(p) ?? true),\n command: ({ editor, range, props }) => {\n const name = normalizeTagName(props.name)\n if (!name) return\n // Reuse a following space instead of doubling it (as Mention does).\n const nodeAfter = editor.view.state.selection.$to.nodeAfter\n if (nodeAfter?.text?.startsWith(' ')) range.to += 1\n editor.chain().focus()\n .insertContentAt(range, [{ type: this.name, attrs: { name } }, { type: 'text', text: ' ' }])\n .run()\n editor.view.dom.ownerDocument.defaultView?.getSelection()?.collapseToEnd()\n if ((props as TagSuggestionItem).isNew) this.options.onCreate?.(editor, name)\n },\n ...rest,\n editor: this.editor,\n })]\n },\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tag.js","names":[],"sources":["../../../src/features/mentions/tag.ts"],"sourcesContent":["import type { Editor, JSONContent } from '@tiptap/core'\nimport { mergeAttributes, Node } from '@tiptap/core'\nimport { PluginKey } from '@tiptap/pm/state'\nimport { Suggestion, type SuggestionOptions } from '@tiptap/suggestion'\nimport { TYPED_BLOCK_TAGS } from '../../core/schema'\n\n/** Attributes of a tag chip; `name` is always normalised (see `normalizeTagName`). */\nexport type TagAttrs = { name: string }\n\n/** A `#` popup row: an existing workspace tag, or (`isNew`) the offer to create the typed one. Picking passes it on as `TagAttrs`. */\nexport type TagSuggestionItem = TagAttrs & { isNew: boolean }\n\nexport const TAG_NODE = 'tag'\nconst MAX_TAG_LENGTH = 64\n\n/**\n * Canonical tag name: lowercase, trimmed, whitespace runs → `-`, only letters (incl. umlauts/ß), digits, `-` and `_`,\n * repeated/edge dashes collapsed. `' Neu Tag! '` → `'neu-tag'`; `''` means \"no usable name\".\n */\nexport function normalizeTagName(raw: string): string {\n return raw.trim().toLowerCase()\n .replace(/\\s+/g, '-')\n .replace(/[^\\p{L}\\p{N}_-]/gu, '')\n .replace(/-{2,}/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, MAX_TAG_LENGTH)\n}\n\n/** Adds every tag name found in a TipTap JSON tree to `into`: tag chips and the tags rows of composites. */\nexport function collectTagNames(content: JSONContent | null | undefined, into: Set<string>): Set<string> {\n if (!content) return into\n if (content.type === TAG_NODE && typeof content.attrs?.name === 'string' && content.attrs.name) into.add(content.attrs.name)\n if (content.type === TYPED_BLOCK_TAGS && Array.isArray(content.attrs?.tags)) {\n for (const name of content.attrs.tags) if (typeof name === 'string' && name) into.add(name)\n }\n for (const child of content.content ?? []) collectTagNames(child, into)\n return into\n}\n\n/**\n * Turns `#name` tokens in text nodes into tag chips — for AI-generated Markdown, where chips come back as their\n * Markdown form `#name` (see renderMarkdown). Only `names` (normalised tag names, e.g. the tags already in the\n * document) match, case-insensitively; other `#words`, `C#` and text in code blocks, inline code or links stay plain.\n * Returns the input unchanged when nothing matched.\n */\nexport function tagsFromText(json: JSONContent, names: ReadonlySet<string>): JSONContent {\n if (!names.size) return json\n // Not preceded by a word char, `#`, `&` or `/` (URL fragments, entities); the whole name up to the next non-name char.\n const token = /(?<![\\p{L}\\p{N}_#&/])#([\\p{L}\\p{N}_-]+)/gu\n\n const splitText = (node: JSONContent): JSONContent[] => {\n const text = node.text ?? ''\n if (!text.includes('#') || node.marks?.some(m => m.type === 'code' || m.type === 'link')) return [node]\n const out: JSONContent[] = []\n let last = 0\n for (const match of text.matchAll(token)) {\n const name = match[1]!.toLowerCase()\n if (!names.has(name)) continue\n const at = match.index ?? 0\n if (at > last) out.push({ ...node, text: text.slice(last, at) })\n out.push({ type: TAG_NODE, attrs: { name }, ...(node.marks?.length ? { marks: node.marks } : {}) })\n last = at + match[0].length\n }\n if (!out.length) return [node]\n if (last < text.length) out.push({ ...node, text: text.slice(last) })\n return out\n }\n\n const walk = (node: JSONContent): JSONContent => {\n if (!node.content || node.type === 'codeBlock') return node\n let changed = false\n const content = node.content.flatMap((child) => {\n const next = child.type === 'text' ? splitText(child) : [walk(child)]\n if (next.length !== 1 || next[0] !== child) changed = true\n return next\n })\n return changed ? { ...node, content } : node\n }\n return walk(json)\n}\n\nexport interface TagOptions {\n HTMLAttributes: Record<string, unknown>\n /** `#` popup (client only, see tag-suggestion.ts); `null` = schema only (server, Markdown). */\n suggestion: Omit<SuggestionOptions<TagSuggestionItem, TagAttrs>, 'editor'> | null\n /** Called after a chip for a newly created tag (`isNew` row) was inserted, e.g. to register it in the metadata. */\n onCreate: ((editor: Editor, name: string) => void) | null\n}\n\n/** Own key, so the `#` popup runs next to the `@` mention popup (Mention uses the default suggestion key). */\nexport const TagSuggestionPluginKey = new PluginKey('tagSuggestion')\n\n/**\n * Inline `#name` chip, shared by the client editor and the server (collab schema, Markdown).\n * The client adds the `#` popup via `.configure({ suggestion: tagSuggestion })` in the mentions feature.\n */\nexport const TagNode = Node.create<TagOptions>({\n name: TAG_NODE,\n group: 'inline',\n inline: true,\n atom: true,\n selectable: false,\n // Above paragraph-level parse rules, like Mention.\n priority: 101,\n\n addOptions() {\n return { HTMLAttributes: { class: 'tag' }, suggestion: null, onCreate: null }\n },\n\n addAttributes() {\n return {\n name: {\n default: '',\n parseHTML: el => normalizeTagName(el.getAttribute('data-name') ?? el.textContent ?? ''),\n renderHTML: attrs => ({ 'data-name': attrs.name }),\n },\n }\n },\n\n parseHTML() {\n return [{ tag: `span[data-type=\"${this.name}\"]` }]\n },\n\n renderHTML({ node, HTMLAttributes }) {\n return ['span', mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes), `#${node.attrs.name}`]\n },\n\n renderText: ({ node }) => `#${node.attrs.name}`,\n\n // Markdown export (editor.getMarkdown(), AI context/selection) shows the chip as plain `#name`.\n renderMarkdown: node => `#${node.attrs?.name ?? ''}`,\n\n addKeyboardShortcuts() {\n return {\n // Backspace right after a chip turns it back into editable `#name` text (which reopens the popup).\n Backspace: () => this.editor.commands.command(({ tr, state }) => {\n const { empty, $anchor } = state.selection\n const before = $anchor.nodeBefore\n if (!empty || before?.type.name !== this.name) return false\n tr.insertText(`#${before.attrs.name}`, $anchor.pos - before.nodeSize, $anchor.pos)\n return true\n }),\n }\n },\n\n addProseMirrorPlugins() {\n const { suggestion } = this.options\n if (!suggestion) return []\n const { allow, ...rest } = suggestion\n // `# `/`## ` at line start stay heading input rules, and `#` inside words (`C#`) never opens the popup.\n const builtInAllow: NonNullable<typeof allow> = ({ state, range }) => !/^[\\s#]/.test(state.doc.textBetween(range.from + 1, range.to))\n return [Suggestion<TagSuggestionItem, TagAttrs>({\n pluginKey: TagSuggestionPluginKey,\n char: '#',\n allowSpaces: true,\n allow: p => builtInAllow(p) && (allow?.(p) ?? true),\n command: ({ editor, range, props }) => {\n const name = normalizeTagName(props.name)\n if (!name) return\n // Reuse a following space instead of doubling it (as Mention does).\n const nodeAfter = editor.view.state.selection.$to.nodeAfter\n if (nodeAfter?.text?.startsWith(' ')) range.to += 1\n editor.chain().focus()\n .insertContentAt(range, [{ type: this.name, attrs: { name } }, { type: 'text', text: ' ' }])\n .run()\n editor.view.dom.ownerDocument.defaultView?.getSelection()?.collapseToEnd()\n if ((props as TagSuggestionItem).isNew) this.options.onCreate?.(editor, name)\n },\n ...rest,\n editor: this.editor,\n })]\n },\n})\n"],"mappings":"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"}
|
|
@@ -13,7 +13,7 @@ const _hoisted_4 = {
|
|
|
13
13
|
key: 0,
|
|
14
14
|
class: "truncate text-xs text-[var(--sk-muted)]"
|
|
15
15
|
};
|
|
16
|
-
/** „
|
|
16
|
+
/** „Theme“: the Obsidian theme of the whole app (`appearance.theme`); „Standard“ is the Nuxt UI look. */
|
|
17
17
|
const STANDARD = "__standard__";
|
|
18
18
|
const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
19
19
|
__name: "SettingsAppearanceTab",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SettingsAppearanceTab.js","names":["_openBlock","_createElementBlock","_createVNode","SettingsSection","_unref","_withCtx","_createElementVNode","_withModifiers","_createBlock","_Fragment","_renderList","_toDisplayString","_createCommentVNode"],"sources":["../../../../src/features/themes/components/SettingsAppearanceTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onMounted, ref, shallowRef } from 'vue'\nimport SettingsSection from '../../../vue/components/SettingsSection.vue'\nimport { fetchErrorMessage } from '../../../core/fetch-error'\nimport { useEditorContext } from '../../../vue/context'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorSettings } from '../../../vue/settings'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport type { ThemeRecord } from '../schema'\n\n/** „Darstellung“: the Obsidian theme of the whole app (`appearance.theme`); „Standard“ is the Nuxt UI look. */\nconst { settings } = useEditorSettings()\nconst { backend } = useEditorContext()\nconst { Badge: UBadge, Button: UButton, FormField: UFormField, Input: UInput, Select: USelect, notifier } = useUiKit()\nconst notify = notifier()\nconst { t } = useEditorI18n()\n\n/** USelect has no `null` value: „Standard“ travels as this sentinel (no theme id can contain underscores). */\nconst STANDARD = '__standard__'\n\nconst themes = shallowRef<ThemeRecord[]>([])\n\nasync function loadThemes() {\n themes.value = await backend.themes?.list() ?? []\n}\n\nonMounted(async () => {\n try {\n await loadThemes()\n }\n catch (error) {\n console.warn('tiptap-editor: Die Themes konnten nicht geladen werden', error)\n }\n})\n\nconst items = computed(() => [\n { label: t('themes.standard'), value: STANDARD },\n ...themes.value.map(({ id, name, author }) => ({ label: author ? `${name} – ${author}` : name, value: id })),\n])\n\nconst theme = computed({\n get: () => settings.value.appearance.theme ?? STANDARD,\n set: (value: string) => {\n settings.value.appearance.theme = value === STANDARD ? null : value\n },\n})\n\nconst cssFile = shallowRef<File | null>(null)\nconst manifestFile = shallowRef<File | null>(null)\nconst uploading = ref(false)\n/** Bumped after an upload to remount the file inputs (a file input cannot be cleared through its model). */\nconst inputKey = ref(0)\n\nfunction pickedFile(event: Event): File | null {\n return (event.target as HTMLInputElement).files?.[0] ?? null\n}\n\nasync function upload() {\n const css = cssFile.value\n if (!css || uploading.value) return\n uploading.value = true\n try {\n // The themes feature needs the `themes` capability, so the tab is only mounted where the backend has it.\n const record = await backend.themes!.store({ css, ...(manifestFile.value && { manifest: manifestFile.value }) })\n cssFile.value = null\n manifestFile.value = null\n inputKey.value++\n settings.value.appearance.theme = record.id\n notify({ title: t('themes.upload.done', { name: record.name }), kind: 'success' })\n try {\n await loadThemes()\n }\n catch (error) {\n console.warn('tiptap-editor: Die Themes konnten nicht geladen werden', error)\n }\n }\n catch (error) {\n notify({ title: t('themes.upload.failed'), description: fetchErrorMessage(error), kind: 'error' })\n }\n finally {\n uploading.value = false\n }\n}\n\nconst removing = ref<string | null>(null)\n\nasync function remove(record: ThemeRecord) {\n if (removing.value) return\n removing.value = record.id\n try {\n await backend.themes!.remove(record.id)\n if (settings.value.appearance.theme === record.id) settings.value.appearance.theme = null\n notify({ title: t('themes.delete.done', { name: record.name }), kind: 'success' })\n try {\n await loadThemes()\n }\n catch (error) {\n console.warn('tiptap-editor: Die Themes konnten nicht geladen werden', error)\n }\n }\n catch (error) {\n notify({ title: t('themes.delete.failed'), description: fetchErrorMessage(error), kind: 'error' })\n }\n finally {\n removing.value = null\n }\n}\n</script>\n\n<template>\n <div class=\"flex flex-col gap-8\">\n <SettingsSection :title=\"t('themes.title')\">\n <UFormField\n orientation=\"horizontal\"\n class=\"py-3\"\n :label=\"t('themes.select.label')\"\n :description=\"t('themes.select.description')\"\n >\n <USelect v-model=\"theme\" :items=\"items\" class=\"w-64\" />\n </UFormField>\n\n <UFormField\n orientation=\"horizontal\"\n class=\"py-3\"\n :label=\"t('themes.upload.label')\"\n :description=\"t('themes.upload.description')\"\n >\n <form class=\"flex w-64 flex-col gap-2\" @submit.prevent=\"upload\">\n <UFormField :label=\"t('themes.upload.css')\" required size=\"xs\">\n <UInput\n :key=\"`css-${inputKey}`\"\n type=\"file\"\n accept=\".css,text/css\"\n class=\"w-full\"\n :disabled=\"uploading\"\n @change=\"cssFile = pickedFile($event)\"\n />\n </UFormField>\n <UFormField :label=\"t('themes.upload.manifest')\" size=\"xs\">\n <UInput\n :key=\"`manifest-${inputKey}`\"\n type=\"file\"\n accept=\".json,application/json\"\n class=\"w-full\"\n :disabled=\"uploading\"\n @change=\"manifestFile = pickedFile($event)\"\n />\n </UFormField>\n <UButton\n type=\"submit\"\n icon=\"i-lucide-upload\"\n :label=\"t('themes.upload.submit')\"\n class=\"self-start\"\n :disabled=\"!cssFile\"\n :loading=\"uploading\"\n />\n </form>\n </UFormField>\n </SettingsSection>\n\n <SettingsSection v-if=\"themes.length\" :title=\"t('themes.library')\">\n <div v-for=\"record in themes\" :key=\"record.id\" class=\"flex items-center gap-3 py-2\">\n <div class=\"flex min-w-0 flex-1 flex-col\">\n <span class=\"truncate text-sm font-medium text-[var(--sk-fg-highlighted)]\">{{ record.name }}</span>\n <span v-if=\"record.version || record.author\" class=\"truncate text-xs text-[var(--sk-muted)]\">\n {{ [record.version && t('themes.version', { version: record.version }), record.author && t('themes.byAuthor', { author: record.author })].filter(Boolean).join(' · ') }}\n </span>\n </div>\n <UBadge v-if=\"record.builtin\" color=\"neutral\" variant=\"subtle\" :label=\"t('themes.builtin')\" />\n <UButton\n v-else\n icon=\"i-lucide-trash-2\"\n variant=\"ghost\"\n color=\"error\"\n :aria-label=\"t('themes.delete.label', { name: record.name })\"\n :loading=\"removing === record.id\"\n :disabled=\"removing !== null\"\n @click=\"remove(record)\"\n />\n </div>\n </SettingsSection>\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SettingsAppearanceTab.js","names":["_openBlock","_createElementBlock","_createVNode","SettingsSection","_unref","_withCtx","_createElementVNode","_withModifiers","_createBlock","_Fragment","_renderList","_toDisplayString","_createCommentVNode"],"sources":["../../../../src/features/themes/components/SettingsAppearanceTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onMounted, ref, shallowRef } from 'vue'\nimport SettingsSection from '../../../vue/components/SettingsSection.vue'\nimport { fetchErrorMessage } from '../../../core/fetch-error'\nimport { useEditorContext } from '../../../vue/context'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorSettings } from '../../../vue/settings'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport type { ThemeRecord } from '../schema'\n\n/** „Theme“: the Obsidian theme of the whole app (`appearance.theme`); „Standard“ is the Nuxt UI look. */\nconst { settings } = useEditorSettings()\nconst { backend } = useEditorContext()\nconst { Badge: UBadge, Button: UButton, FormField: UFormField, Input: UInput, Select: USelect, notifier } = useUiKit()\nconst notify = notifier()\nconst { t } = useEditorI18n()\n\n/** USelect has no `null` value: „Standard“ travels as this sentinel (no theme id can contain underscores). */\nconst STANDARD = '__standard__'\n\nconst themes = shallowRef<ThemeRecord[]>([])\n\nasync function loadThemes() {\n themes.value = await backend.themes?.list() ?? []\n}\n\nonMounted(async () => {\n try {\n await loadThemes()\n }\n catch (error) {\n console.warn('tiptap-editor: Die Themes konnten nicht geladen werden', error)\n }\n})\n\nconst items = computed(() => [\n { label: t('themes.standard'), value: STANDARD },\n ...themes.value.map(({ id, name, author }) => ({ label: author ? `${name} – ${author}` : name, value: id })),\n])\n\nconst theme = computed({\n get: () => settings.value.appearance.theme ?? STANDARD,\n set: (value: string) => {\n settings.value.appearance.theme = value === STANDARD ? null : value\n },\n})\n\nconst cssFile = shallowRef<File | null>(null)\nconst manifestFile = shallowRef<File | null>(null)\nconst uploading = ref(false)\n/** Bumped after an upload to remount the file inputs (a file input cannot be cleared through its model). */\nconst inputKey = ref(0)\n\nfunction pickedFile(event: Event): File | null {\n return (event.target as HTMLInputElement).files?.[0] ?? null\n}\n\nasync function upload() {\n const css = cssFile.value\n if (!css || uploading.value) return\n uploading.value = true\n try {\n // The themes feature needs the `themes` capability, so the tab is only mounted where the backend has it.\n const record = await backend.themes!.store({ css, ...(manifestFile.value && { manifest: manifestFile.value }) })\n cssFile.value = null\n manifestFile.value = null\n inputKey.value++\n settings.value.appearance.theme = record.id\n notify({ title: t('themes.upload.done', { name: record.name }), kind: 'success' })\n try {\n await loadThemes()\n }\n catch (error) {\n console.warn('tiptap-editor: Die Themes konnten nicht geladen werden', error)\n }\n }\n catch (error) {\n notify({ title: t('themes.upload.failed'), description: fetchErrorMessage(error), kind: 'error' })\n }\n finally {\n uploading.value = false\n }\n}\n\nconst removing = ref<string | null>(null)\n\nasync function remove(record: ThemeRecord) {\n if (removing.value) return\n removing.value = record.id\n try {\n await backend.themes!.remove(record.id)\n if (settings.value.appearance.theme === record.id) settings.value.appearance.theme = null\n notify({ title: t('themes.delete.done', { name: record.name }), kind: 'success' })\n try {\n await loadThemes()\n }\n catch (error) {\n console.warn('tiptap-editor: Die Themes konnten nicht geladen werden', error)\n }\n }\n catch (error) {\n notify({ title: t('themes.delete.failed'), description: fetchErrorMessage(error), kind: 'error' })\n }\n finally {\n removing.value = null\n }\n}\n</script>\n\n<template>\n <div class=\"flex flex-col gap-8\">\n <SettingsSection :title=\"t('themes.title')\">\n <UFormField\n orientation=\"horizontal\"\n class=\"py-3\"\n :label=\"t('themes.select.label')\"\n :description=\"t('themes.select.description')\"\n >\n <USelect v-model=\"theme\" :items=\"items\" class=\"w-64\" />\n </UFormField>\n\n <UFormField\n orientation=\"horizontal\"\n class=\"py-3\"\n :label=\"t('themes.upload.label')\"\n :description=\"t('themes.upload.description')\"\n >\n <form class=\"flex w-64 flex-col gap-2\" @submit.prevent=\"upload\">\n <UFormField :label=\"t('themes.upload.css')\" required size=\"xs\">\n <UInput\n :key=\"`css-${inputKey}`\"\n type=\"file\"\n accept=\".css,text/css\"\n class=\"w-full\"\n :disabled=\"uploading\"\n @change=\"cssFile = pickedFile($event)\"\n />\n </UFormField>\n <UFormField :label=\"t('themes.upload.manifest')\" size=\"xs\">\n <UInput\n :key=\"`manifest-${inputKey}`\"\n type=\"file\"\n accept=\".json,application/json\"\n class=\"w-full\"\n :disabled=\"uploading\"\n @change=\"manifestFile = pickedFile($event)\"\n />\n </UFormField>\n <UButton\n type=\"submit\"\n icon=\"i-lucide-upload\"\n :label=\"t('themes.upload.submit')\"\n class=\"self-start\"\n :disabled=\"!cssFile\"\n :loading=\"uploading\"\n />\n </form>\n </UFormField>\n </SettingsSection>\n\n <SettingsSection v-if=\"themes.length\" :title=\"t('themes.library')\">\n <div v-for=\"record in themes\" :key=\"record.id\" class=\"flex items-center gap-3 py-2\">\n <div class=\"flex min-w-0 flex-1 flex-col\">\n <span class=\"truncate text-sm font-medium text-[var(--sk-fg-highlighted)]\">{{ record.name }}</span>\n <span v-if=\"record.version || record.author\" class=\"truncate text-xs text-[var(--sk-muted)]\">\n {{ [record.version && t('themes.version', { version: record.version }), record.author && t('themes.byAuthor', { author: record.author })].filter(Boolean).join(' · ') }}\n </span>\n </div>\n <UBadge v-if=\"record.builtin\" color=\"neutral\" variant=\"subtle\" :label=\"t('themes.builtin')\" />\n <UButton\n v-else\n icon=\"i-lucide-trash-2\"\n variant=\"ghost\"\n color=\"error\"\n :aria-label=\"t('themes.delete.label', { name: record.name })\"\n :loading=\"removing === record.id\"\n :disabled=\"removing !== null\"\n @click=\"remove(record)\"\n />\n </div>\n </SettingsSection>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import "../../core/index.js";
|
|
|
3
3
|
//#region src/features/themes/feature.d.ts
|
|
4
4
|
/** What a framework binding supplies to the themes feature. */
|
|
5
5
|
export interface ThemesUi<C> {
|
|
6
|
-
/** The „
|
|
6
|
+
/** The „Theme“ settings tab component of the framework. */
|
|
7
7
|
settingsTab: C;
|
|
8
8
|
/**
|
|
9
9
|
* Decides how long the theme runtime lives. Receives the feature context and `start` (starts the shared, reference-counted
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/themes/feature.ts"],"sourcesContent":["import type { EditorFeature, FeatureContext } from '../../core'\nimport { startObsidianTheme } from './theme-runtime'\n\n/** What a framework binding supplies to the themes feature. */\nexport interface ThemesUi<C> {\n /** The „
|
|
1
|
+
{"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/themes/feature.ts"],"sourcesContent":["import type { EditorFeature, FeatureContext } from '../../core'\nimport { startObsidianTheme } from './theme-runtime'\n\n/** What a framework binding supplies to the themes feature. */\nexport interface ThemesUi<C> {\n /** The „Theme“ settings tab component of the framework. */\n settingsTab: C\n /**\n * Decides how long the theme runtime lives. Receives the feature context and `start` (starts the shared, reference-counted\n * runtime and returns its idempotent release); its return value is the feature's `setup` result (the teardown run when\n * the surface unmounts). Default: `start()` itself, i.e. the runtime lives as long as the surface is mounted.\n */\n lifetime?: (ctx: FeatureContext, start: () => () => void) => void | (() => void)\n}\n\n/**\n * Site-wide Obsidian themes: the theme named by the setting `appearance.theme` (`null` = the standard look) is\n * fetched from `GET /api/editor/themes/:id`, scoped to `.obsidian-theme` and injected as one `<style data-tiptap-theme>`\n * in the document head. `<html>` carries `obsidian-theme theme-dark|theme-light` following the app's colour mode\n * (`html.dark`, else the system preference), so the bridge stylesheet `styles/themes.css` maps Obsidian's variables onto\n * every UI token (the whole app) and onto the editor content. Needs an adapter with `listThemes`/`loadTheme`.\n */\nexport function themesFeatureCore<C>(ui: ThemesUi<C>): EditorFeature<C> {\n return {\n id: 'themes',\n dimension: 'interaction',\n backend: ['themes'],\n setup: ctx => (ui.lifetime ? ui.lifetime(ctx, () => startObsidianTheme(ctx)) : startObsidianTheme(ctx)),\n settingsTabs: ctx => [{ id: 'appearance', label: ctx.t('themes.tab'), icon: 'i-lucide-palette', component: ui.settingsTab }],\n }\n}\n"],"mappings":";;;;;;;;;AAsBA,SAAgB,kBAAqB,IAAmC;CACtE,OAAO;EACL,IAAI;EACJ,WAAW;EACX,SAAS,CAAC,QAAQ;EAClB,QAAO,QAAQ,GAAG,WAAW,GAAG,SAAS,WAAW,mBAAmB,GAAG,CAAC,IAAI,mBAAmB,GAAG;EACrG,eAAc,QAAO,CAAC;GAAE,IAAI;GAAc,OAAO,IAAI,EAAE,YAAY;GAAG,MAAM;GAAoB,WAAW,GAAG;EAAY,CAAC;CAC7H;AACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/features/themes/react/SettingsAppearanceTab.d.ts
|
|
2
|
-
/** „
|
|
2
|
+
/** „Theme“: the Obsidian theme of the whole app (`appearance.theme`); „Standard“ is the normal look. */
|
|
3
3
|
export declare function SettingsAppearanceTab(): import("react").JSX.Element;
|
|
4
4
|
//#endregion
|
|
5
5
|
//# sourceMappingURL=SettingsAppearanceTab.d.ts.map
|
|
@@ -33,7 +33,7 @@ function FormField({ label, description, required, small, horizontal, children }
|
|
|
33
33
|
}), children]
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
-
/** „
|
|
36
|
+
/** „Theme“: the Obsidian theme of the whole app (`appearance.theme`); „Standard“ is the normal look. */
|
|
37
37
|
function SettingsAppearanceTab() {
|
|
38
38
|
const { settings, update } = useEditorSettings();
|
|
39
39
|
const { backend, notify } = useEditorContext();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SettingsAppearanceTab.js","names":[],"sources":["../../../../src/features/themes/react/SettingsAppearanceTab.tsx"],"sourcesContent":["import { type ReactNode, useCallback, useEffect, useState } from 'react'\nimport { fetchErrorMessage } from '../../../core/fetch-error'\nimport { useEditorContext } from '../../../react/context'\nimport { Icon } from '../../../react/Icon'\nimport { useEditorSettings } from '../../../react/settings'\nimport { SettingsSection } from '../../../react/SettingsSection'\nimport { Badge } from '../../../react/ui/badge'\nimport { Button } from '../../../react/ui/button'\nimport { Input } from '../../../react/ui/input'\nimport { Label } from '../../../react/ui/label'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../react/ui/select'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport type { ThemeRecord } from '../schema'\n\n/** Radix Select has no `null`/empty value: „Standard“ travels as this sentinel (no theme id can contain underscores). */\nconst STANDARD = '__standard__'\n\n/** A label (+ description) next to or above its control; the counterpart of Nuxt UI's `UFormField`. */\nfunction FormField({ label, description, required, small, horizontal, children }: {\n label: string\n description?: string\n required?: boolean\n small?: boolean\n horizontal?: boolean\n children: ReactNode\n}) {\n return (\n <div className={horizontal ? 'flex items-start justify-between gap-4 py-3' : 'flex flex-col gap-1.5'}>\n <div className={horizontal ? 'min-w-0 flex-1' : undefined}>\n <Label className={small ? 'text-xs font-medium text-foreground' : 'text-sm font-medium text-foreground'}>\n {label}\n {required && <span className=\"ms-0.5 text-destructive\">*</span>}\n </Label>\n {description && <p className=\"text-sm text-muted-foreground\">{description}</p>}\n </div>\n {children}\n </div>\n )\n}\n\n/** „Darstellung“: the Obsidian theme of the whole app (`appearance.theme`); „Standard“ is the normal look. */\nexport function SettingsAppearanceTab() {\n const { settings, update } = useEditorSettings()\n const { backend, notify } = useEditorContext()\n const { t } = useEditorI18n()\n\n const [themes, setThemes] = useState<ThemeRecord[]>([])\n\n const loadThemes = useCallback(async () => {\n setThemes(await backend.themes?.list() ?? [])\n }, [backend])\n\n useEffect(() => {\n loadThemes().catch((error: unknown) => {\n console.warn('tiptap-editor: Die Themes konnten nicht geladen werden', error)\n })\n }, [loadThemes])\n\n const setTheme = (value: string | null) => update((draft) => { draft.appearance.theme = value })\n\n const [cssFile, setCssFile] = useState<File | null>(null)\n const [manifestFile, setManifestFile] = useState<File | null>(null)\n const [uploading, setUploading] = useState(false)\n /** Bumped after an upload to remount the file inputs (a file input cannot be cleared through its value). */\n const [inputKey, setInputKey] = useState(0)\n\n async function upload() {\n if (!cssFile || uploading) return\n setUploading(true)\n try {\n // The themes feature needs the `themes` capability, so the tab is only mounted where the backend has it.\n const record = await backend.themes!.store({ css: cssFile, ...(manifestFile && { manifest: manifestFile }) })\n setCssFile(null)\n setManifestFile(null)\n setInputKey(key => key + 1)\n setTheme(record.id)\n notify({ title: t('themes.upload.done', { name: record.name }), kind: 'success' })\n try {\n await loadThemes()\n }\n catch (error) {\n console.warn('tiptap-editor: Die Themes konnten nicht geladen werden', error)\n }\n }\n catch (error) {\n notify({ title: t('themes.upload.failed'), description: fetchErrorMessage(error), kind: 'error' })\n }\n finally {\n setUploading(false)\n }\n }\n\n const [removing, setRemoving] = useState<string | null>(null)\n\n async function remove(record: ThemeRecord) {\n if (removing) return\n setRemoving(record.id)\n try {\n await backend.themes!.remove(record.id)\n if (settings.appearance.theme === record.id) setTheme(null)\n notify({ title: t('themes.delete.done', { name: record.name }), kind: 'success' })\n try {\n await loadThemes()\n }\n catch (error) {\n console.warn('tiptap-editor: Die Themes konnten nicht geladen werden', error)\n }\n }\n catch (error) {\n notify({ title: t('themes.delete.failed'), description: fetchErrorMessage(error), kind: 'error' })\n }\n finally {\n setRemoving(null)\n }\n }\n\n return (\n <div className=\"flex flex-col gap-8\">\n <SettingsSection title={t('themes.title')}>\n <FormField\n horizontal\n label={t('themes.select.label')}\n description={t('themes.select.description')}\n >\n <Select value={settings.appearance.theme ?? STANDARD} onValueChange={value => setTheme(value === STANDARD ? null : value)}>\n <SelectTrigger className=\"w-64\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={STANDARD}>{t('themes.standard')}</SelectItem>\n {themes.map(({ id, name, author }) => (\n <SelectItem key={id} value={id}>{author ? `${name} – ${author}` : name}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n </FormField>\n\n <FormField\n horizontal\n label={t('themes.upload.label')}\n description={t('themes.upload.description')}\n >\n <form\n className=\"flex w-64 flex-col gap-2\"\n onSubmit={(event) => {\n event.preventDefault()\n void upload()\n }}\n >\n <FormField label={t('themes.upload.css')} required small>\n <Input\n key={`css-${inputKey}`}\n type=\"file\"\n accept=\".css,text/css\"\n className=\"w-full\"\n disabled={uploading}\n onChange={event => setCssFile(event.target.files?.[0] ?? null)}\n />\n </FormField>\n <FormField label={t('themes.upload.manifest')} small>\n <Input\n key={`manifest-${inputKey}`}\n type=\"file\"\n accept=\".json,application/json\"\n className=\"w-full\"\n disabled={uploading}\n onChange={event => setManifestFile(event.target.files?.[0] ?? null)}\n />\n </FormField>\n <Button type=\"submit\" className=\"self-start\" disabled={!cssFile || uploading}>\n <Icon name={uploading ? 'i-lucide-loader-circle' : 'i-lucide-upload'} className={uploading ? 'animate-spin' : undefined} />\n {t('themes.upload.submit')}\n </Button>\n </form>\n </FormField>\n </SettingsSection>\n\n {themes.length > 0 && (\n <SettingsSection title={t('themes.library')}>\n {themes.map(record => (\n <div key={record.id} className=\"flex items-center gap-3 py-2\">\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"truncate text-sm font-medium text-foreground\">{record.name}</span>\n {(record.version || record.author) && (\n <span className=\"truncate text-xs text-muted-foreground\">\n {[record.version && t('themes.version', { version: record.version }), record.author && t('themes.byAuthor', { author: record.author })].filter(Boolean).join(' · ')}\n </span>\n )}\n </div>\n {record.builtin\n ? <Badge variant=\"secondary\">{t('themes.builtin')}</Badge>\n : (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"text-destructive\"\n aria-label={t('themes.delete.label', { name: record.name })}\n disabled={removing !== null}\n onClick={() => void remove(record)}\n >\n <Icon name={removing === record.id ? 'i-lucide-loader-circle' : 'i-lucide-trash-2'} className={removing === record.id ? 'animate-spin' : undefined} />\n </Button>\n )}\n </div>\n ))}\n </SettingsSection>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SettingsAppearanceTab.js","names":[],"sources":["../../../../src/features/themes/react/SettingsAppearanceTab.tsx"],"sourcesContent":["import { type ReactNode, useCallback, useEffect, useState } from 'react'\nimport { fetchErrorMessage } from '../../../core/fetch-error'\nimport { useEditorContext } from '../../../react/context'\nimport { Icon } from '../../../react/Icon'\nimport { useEditorSettings } from '../../../react/settings'\nimport { SettingsSection } from '../../../react/SettingsSection'\nimport { Badge } from '../../../react/ui/badge'\nimport { Button } from '../../../react/ui/button'\nimport { Input } from '../../../react/ui/input'\nimport { Label } from '../../../react/ui/label'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../react/ui/select'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport type { ThemeRecord } from '../schema'\n\n/** Radix Select has no `null`/empty value: „Standard“ travels as this sentinel (no theme id can contain underscores). */\nconst STANDARD = '__standard__'\n\n/** A label (+ description) next to or above its control; the counterpart of Nuxt UI's `UFormField`. */\nfunction FormField({ label, description, required, small, horizontal, children }: {\n label: string\n description?: string\n required?: boolean\n small?: boolean\n horizontal?: boolean\n children: ReactNode\n}) {\n return (\n <div className={horizontal ? 'flex items-start justify-between gap-4 py-3' : 'flex flex-col gap-1.5'}>\n <div className={horizontal ? 'min-w-0 flex-1' : undefined}>\n <Label className={small ? 'text-xs font-medium text-foreground' : 'text-sm font-medium text-foreground'}>\n {label}\n {required && <span className=\"ms-0.5 text-destructive\">*</span>}\n </Label>\n {description && <p className=\"text-sm text-muted-foreground\">{description}</p>}\n </div>\n {children}\n </div>\n )\n}\n\n/** „Theme“: the Obsidian theme of the whole app (`appearance.theme`); „Standard“ is the normal look. */\nexport function SettingsAppearanceTab() {\n const { settings, update } = useEditorSettings()\n const { backend, notify } = useEditorContext()\n const { t } = useEditorI18n()\n\n const [themes, setThemes] = useState<ThemeRecord[]>([])\n\n const loadThemes = useCallback(async () => {\n setThemes(await backend.themes?.list() ?? [])\n }, [backend])\n\n useEffect(() => {\n loadThemes().catch((error: unknown) => {\n console.warn('tiptap-editor: Die Themes konnten nicht geladen werden', error)\n })\n }, [loadThemes])\n\n const setTheme = (value: string | null) => update((draft) => { draft.appearance.theme = value })\n\n const [cssFile, setCssFile] = useState<File | null>(null)\n const [manifestFile, setManifestFile] = useState<File | null>(null)\n const [uploading, setUploading] = useState(false)\n /** Bumped after an upload to remount the file inputs (a file input cannot be cleared through its value). */\n const [inputKey, setInputKey] = useState(0)\n\n async function upload() {\n if (!cssFile || uploading) return\n setUploading(true)\n try {\n // The themes feature needs the `themes` capability, so the tab is only mounted where the backend has it.\n const record = await backend.themes!.store({ css: cssFile, ...(manifestFile && { manifest: manifestFile }) })\n setCssFile(null)\n setManifestFile(null)\n setInputKey(key => key + 1)\n setTheme(record.id)\n notify({ title: t('themes.upload.done', { name: record.name }), kind: 'success' })\n try {\n await loadThemes()\n }\n catch (error) {\n console.warn('tiptap-editor: Die Themes konnten nicht geladen werden', error)\n }\n }\n catch (error) {\n notify({ title: t('themes.upload.failed'), description: fetchErrorMessage(error), kind: 'error' })\n }\n finally {\n setUploading(false)\n }\n }\n\n const [removing, setRemoving] = useState<string | null>(null)\n\n async function remove(record: ThemeRecord) {\n if (removing) return\n setRemoving(record.id)\n try {\n await backend.themes!.remove(record.id)\n if (settings.appearance.theme === record.id) setTheme(null)\n notify({ title: t('themes.delete.done', { name: record.name }), kind: 'success' })\n try {\n await loadThemes()\n }\n catch (error) {\n console.warn('tiptap-editor: Die Themes konnten nicht geladen werden', error)\n }\n }\n catch (error) {\n notify({ title: t('themes.delete.failed'), description: fetchErrorMessage(error), kind: 'error' })\n }\n finally {\n setRemoving(null)\n }\n }\n\n return (\n <div className=\"flex flex-col gap-8\">\n <SettingsSection title={t('themes.title')}>\n <FormField\n horizontal\n label={t('themes.select.label')}\n description={t('themes.select.description')}\n >\n <Select value={settings.appearance.theme ?? STANDARD} onValueChange={value => setTheme(value === STANDARD ? null : value)}>\n <SelectTrigger className=\"w-64\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={STANDARD}>{t('themes.standard')}</SelectItem>\n {themes.map(({ id, name, author }) => (\n <SelectItem key={id} value={id}>{author ? `${name} – ${author}` : name}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n </FormField>\n\n <FormField\n horizontal\n label={t('themes.upload.label')}\n description={t('themes.upload.description')}\n >\n <form\n className=\"flex w-64 flex-col gap-2\"\n onSubmit={(event) => {\n event.preventDefault()\n void upload()\n }}\n >\n <FormField label={t('themes.upload.css')} required small>\n <Input\n key={`css-${inputKey}`}\n type=\"file\"\n accept=\".css,text/css\"\n className=\"w-full\"\n disabled={uploading}\n onChange={event => setCssFile(event.target.files?.[0] ?? null)}\n />\n </FormField>\n <FormField label={t('themes.upload.manifest')} small>\n <Input\n key={`manifest-${inputKey}`}\n type=\"file\"\n accept=\".json,application/json\"\n className=\"w-full\"\n disabled={uploading}\n onChange={event => setManifestFile(event.target.files?.[0] ?? null)}\n />\n </FormField>\n <Button type=\"submit\" className=\"self-start\" disabled={!cssFile || uploading}>\n <Icon name={uploading ? 'i-lucide-loader-circle' : 'i-lucide-upload'} className={uploading ? 'animate-spin' : undefined} />\n {t('themes.upload.submit')}\n </Button>\n </form>\n </FormField>\n </SettingsSection>\n\n {themes.length > 0 && (\n <SettingsSection title={t('themes.library')}>\n {themes.map(record => (\n <div key={record.id} className=\"flex items-center gap-3 py-2\">\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"truncate text-sm font-medium text-foreground\">{record.name}</span>\n {(record.version || record.author) && (\n <span className=\"truncate text-xs text-muted-foreground\">\n {[record.version && t('themes.version', { version: record.version }), record.author && t('themes.byAuthor', { author: record.author })].filter(Boolean).join(' · ')}\n </span>\n )}\n </div>\n {record.builtin\n ? <Badge variant=\"secondary\">{t('themes.builtin')}</Badge>\n : (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"text-destructive\"\n aria-label={t('themes.delete.label', { name: record.name })}\n disabled={removing !== null}\n onClick={() => void remove(record)}\n >\n <Icon name={removing === record.id ? 'i-lucide-loader-circle' : 'i-lucide-trash-2'} className={removing === record.id ? 'animate-spin' : undefined} />\n </Button>\n )}\n </div>\n ))}\n </SettingsSection>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -74,7 +74,7 @@ function createRuntime(ctx) {
|
|
|
74
74
|
applyRootClasses();
|
|
75
75
|
} catch (error) {
|
|
76
76
|
if (ticket !== latest) return;
|
|
77
|
-
console.warn(`tiptap-editor: Theme „${id}“ konnte nicht geladen werden,
|
|
77
|
+
console.warn(`tiptap-editor: Theme „${id}“ konnte nicht geladen werden, es gilt das Standard-Aussehen`, error);
|
|
78
78
|
removeStyle();
|
|
79
79
|
applied = null;
|
|
80
80
|
applyRootClasses();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme-runtime.js","names":[],"sources":["../../../src/features/themes/theme-runtime.ts"],"sourcesContent":["import type { FeatureContext } from '../../core/feature'\nimport { scopeThemeCss } from './scope-css'\n\n/** Class that scopes a theme's rules (`scopeThemeCss`) and the bridge stylesheet; the runtime puts it on `<html>`. */\nexport const THEME_SCOPE_CLASS = 'obsidian-theme'\n\n/** Marks the single `<style>` element in `document.head` that carries the active theme. */\nconst STYLE_ATTRIBUTE = 'data-tiptap-theme'\n\n// One runtime per app (keyed by the app's settings store): every surface of the page shares the style element and the\n// cache. Reference-counted: the last release stops it.\nconst runtimes = new Map<object, { count: number, stop: () => void }>()\n\nconst PREFERS_DARK = '(prefers-color-scheme: dark)'\n\n/** Whether the app is in dark mode: `html.dark` (Nuxt colour mode), `html.light`, else the system preference. */\nfunction readDark(): boolean {\n if (typeof document === 'undefined') return false\n const classes = document.documentElement.classList\n if (classes.contains('dark')) return true\n if (classes.contains('light')) return false\n return typeof matchMedia === 'function' && matchMedia(PREFERS_DARK).matches\n}\n\nfunction createRuntime(ctx: FeatureContext): () => void {\n let dark = readDark()\n /** The theme whose style is in the document; null = standard look. */\n let applied: string | null = null\n const cache = new Map<string, string>()\n let style: HTMLStyleElement | undefined\n let latest = 0\n let teardown: (() => void) | undefined\n let stopTheme: (() => void) | undefined\n\n /** `obsidian-theme theme-dark|theme-light` on `<html>` while a theme is applied; none of the three otherwise (standard look). */\n function applyRootClasses() {\n if (typeof document === 'undefined') return\n const root = document.documentElement.classList\n const on = applied !== null\n root.toggle(THEME_SCOPE_CLASS, on)\n root.toggle('theme-dark', on && dark)\n root.toggle('theme-light', on && !dark)\n }\n\n function removeRootClasses() {\n if (typeof document === 'undefined') return\n document.documentElement.classList.remove(THEME_SCOPE_CLASS, 'theme-dark', 'theme-light')\n }\n\n function removeStyle() {\n style?.remove()\n style = undefined\n }\n\n function showStyle(css: string) {\n if (!style) {\n style = document.createElement('style')\n style.setAttribute(STYLE_ATTRIBUTE, '')\n document.head.appendChild(style)\n }\n style.textContent = css\n }\n\n async function load(id: string): Promise<string> {\n const cached = cache.get(id)\n if (cached !== undefined) return cached\n const themes = ctx.backend.themes\n if (!themes) throw new Error(`@ecsplico/editor: backend \"${ctx.backend.name}\" has no themes capability`)\n const css = await themes.css(id)\n const scoped = scopeThemeCss(css, `.${THEME_SCOPE_CLASS}`)\n cache.set(id, scoped)\n return scoped\n }\n\n async function select(id: string | null) {\n const ticket = ++latest\n if (id === null) {\n removeStyle()\n applied = null\n applyRootClasses()\n return\n }\n try {\n const css = await load(id)\n if (ticket !== latest) return\n showStyle(css)\n applied = id\n applyRootClasses()\n }\n catch (error) {\n if (ticket !== latest) return\n // The stored setting stays untouched: the theme may come back (host restored it), the session shows the standard look.\n console.warn(`tiptap-editor: Theme „${id}“ konnte nicht geladen werden,
|
|
1
|
+
{"version":3,"file":"theme-runtime.js","names":[],"sources":["../../../src/features/themes/theme-runtime.ts"],"sourcesContent":["import type { FeatureContext } from '../../core/feature'\nimport { scopeThemeCss } from './scope-css'\n\n/** Class that scopes a theme's rules (`scopeThemeCss`) and the bridge stylesheet; the runtime puts it on `<html>`. */\nexport const THEME_SCOPE_CLASS = 'obsidian-theme'\n\n/** Marks the single `<style>` element in `document.head` that carries the active theme. */\nconst STYLE_ATTRIBUTE = 'data-tiptap-theme'\n\n// One runtime per app (keyed by the app's settings store): every surface of the page shares the style element and the\n// cache. Reference-counted: the last release stops it.\nconst runtimes = new Map<object, { count: number, stop: () => void }>()\n\nconst PREFERS_DARK = '(prefers-color-scheme: dark)'\n\n/** Whether the app is in dark mode: `html.dark` (Nuxt colour mode), `html.light`, else the system preference. */\nfunction readDark(): boolean {\n if (typeof document === 'undefined') return false\n const classes = document.documentElement.classList\n if (classes.contains('dark')) return true\n if (classes.contains('light')) return false\n return typeof matchMedia === 'function' && matchMedia(PREFERS_DARK).matches\n}\n\nfunction createRuntime(ctx: FeatureContext): () => void {\n let dark = readDark()\n /** The theme whose style is in the document; null = standard look. */\n let applied: string | null = null\n const cache = new Map<string, string>()\n let style: HTMLStyleElement | undefined\n let latest = 0\n let teardown: (() => void) | undefined\n let stopTheme: (() => void) | undefined\n\n /** `obsidian-theme theme-dark|theme-light` on `<html>` while a theme is applied; none of the three otherwise (standard look). */\n function applyRootClasses() {\n if (typeof document === 'undefined') return\n const root = document.documentElement.classList\n const on = applied !== null\n root.toggle(THEME_SCOPE_CLASS, on)\n root.toggle('theme-dark', on && dark)\n root.toggle('theme-light', on && !dark)\n }\n\n function removeRootClasses() {\n if (typeof document === 'undefined') return\n document.documentElement.classList.remove(THEME_SCOPE_CLASS, 'theme-dark', 'theme-light')\n }\n\n function removeStyle() {\n style?.remove()\n style = undefined\n }\n\n function showStyle(css: string) {\n if (!style) {\n style = document.createElement('style')\n style.setAttribute(STYLE_ATTRIBUTE, '')\n document.head.appendChild(style)\n }\n style.textContent = css\n }\n\n async function load(id: string): Promise<string> {\n const cached = cache.get(id)\n if (cached !== undefined) return cached\n const themes = ctx.backend.themes\n if (!themes) throw new Error(`@ecsplico/editor: backend \"${ctx.backend.name}\" has no themes capability`)\n const css = await themes.css(id)\n const scoped = scopeThemeCss(css, `.${THEME_SCOPE_CLASS}`)\n cache.set(id, scoped)\n return scoped\n }\n\n async function select(id: string | null) {\n const ticket = ++latest\n if (id === null) {\n removeStyle()\n applied = null\n applyRootClasses()\n return\n }\n try {\n const css = await load(id)\n if (ticket !== latest) return\n showStyle(css)\n applied = id\n applyRootClasses()\n }\n catch (error) {\n if (ticket !== latest) return\n // The stored setting stays untouched: the theme may come back (host restored it), the session shows the standard look.\n console.warn(`tiptap-editor: Theme „${id}“ konnte nicht geladen werden, es gilt das Standard-Aussehen`, error)\n removeStyle()\n applied = null\n applyRootClasses()\n }\n }\n\n if (typeof document !== 'undefined') {\n void select(ctx.settings.get().appearance.theme)\n let lastTheme = ctx.settings.get().appearance.theme\n stopTheme = ctx.settings.subscribe((value) => {\n if (value.appearance.theme === lastTheme) return\n lastTheme = value.appearance.theme\n void select(lastTheme)\n })\n applyRootClasses()\n const onDarkChange = () => {\n const next = readDark()\n if (next === dark) return\n dark = next\n applyRootClasses()\n }\n const observer = new MutationObserver(onDarkChange)\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })\n const media = typeof matchMedia === 'function' ? matchMedia(PREFERS_DARK) : undefined\n media?.addEventListener('change', onDarkChange)\n teardown = () => {\n observer.disconnect()\n media?.removeEventListener('change', onDarkChange)\n removeStyle()\n }\n }\n\n return () => {\n latest++\n stopTheme?.()\n teardown?.()\n removeRootClasses()\n }\n}\n\n/**\n * The theme runtime of the app `ctx` belongs to: applies `settings.appearance.theme` (fetches the stylesheet through\n * `ctx.backend.themes`, scopes it, keeps one `<style data-tiptap-theme>` in the document head, puts `obsidian-theme theme-dark|theme-light`\n * on `<html>`) and follows the app's colour mode.\n * Reference-counted per app: the first call starts the runtime, every call returns an idempotent release, the last release\n * stops it (removing the style element and the root classes). Surfaces of one app share a single runtime.\n */\nexport function startObsidianTheme(ctx: FeatureContext): () => void {\n const key: object = ctx.settings\n let entry = runtimes.get(key)\n if (!entry) {\n entry = { count: 0, stop: createRuntime(ctx) }\n runtimes.set(key, entry)\n }\n entry.count++\n const held = entry\n let released = false\n return () => {\n if (released) return\n released = true\n if (--held.count > 0) return\n held.stop()\n if (runtimes.get(key) === held) runtimes.delete(key)\n }\n}\n"],"mappings":"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"}
|
|
@@ -127,7 +127,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
127
127
|
props.editor.commands.focus(props.getPos() + props.node.nodeSize - 1);
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
|
-
/** Seeks and plays the source recording of the enclosing
|
|
130
|
+
/** Seeks and plays the source recording of the enclosing Typed block (a no-op without the composites feature or a source). */
|
|
131
131
|
function seek() {
|
|
132
132
|
const start = props.node.attrs.start;
|
|
133
133
|
const pos = props.getPos();
|