@ecsplico/editor 3.5.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (583) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/dist/adapters/direct-ai.d.ts +4 -4
  3. package/dist/adapters/direct-ai.js +2 -2
  4. package/dist/adapters/direct-ai.js.map +1 -1
  5. package/dist/adapters/document-state.d.ts +1 -1
  6. package/dist/adapters/document-state.js.map +1 -1
  7. package/dist/adapters/forge.d.ts +2 -2
  8. package/dist/adapters/forge.js.map +1 -1
  9. package/dist/adapters/nitro.d.ts +11 -3
  10. package/dist/adapters/nitro.js +3 -1
  11. package/dist/adapters/nitro.js.map +1 -1
  12. package/dist/adapters/shared.d.ts +6 -6
  13. package/dist/adapters/shared.js.map +1 -1
  14. package/dist/code/CodeSurface.vue.d.ts +3 -3
  15. package/dist/config/index.d.ts +14 -14
  16. package/dist/config/index.js +8 -8
  17. package/dist/config/index.js.map +1 -1
  18. package/dist/core/backend-types.d.ts +3 -3
  19. package/dist/core/backend-types.js.map +1 -1
  20. package/dist/core/backend.d.ts +4 -4
  21. package/dist/core/backend.js.map +1 -1
  22. package/dist/core/block-convert.d.ts +1 -1
  23. package/dist/core/block-convert.js +5 -5
  24. package/dist/core/block-convert.js.map +1 -1
  25. package/dist/core/block-transfer.d.ts +8 -4
  26. package/dist/core/block-transfer.js +23 -14
  27. package/dist/core/block-transfer.js.map +1 -1
  28. package/dist/core/capabilities.d.ts +113 -0
  29. package/dist/core/capabilities.js +155 -0
  30. package/dist/core/capabilities.js.map +1 -0
  31. package/dist/core/command-types.d.ts +1 -1
  32. package/dist/core/context.js.map +1 -1
  33. package/dist/core/convert.d.ts +5 -5
  34. package/dist/core/convert.js +5 -5
  35. package/dist/core/convert.js.map +1 -1
  36. package/dist/core/core-feature.js +1 -1
  37. package/dist/core/define-editor.d.ts +2 -2
  38. package/dist/core/define-editor.js +34 -6
  39. package/dist/core/define-editor.js.map +1 -1
  40. package/dist/core/derivation.d.ts +119 -0
  41. package/dist/core/derivation.js +187 -0
  42. package/dist/core/derivation.js.map +1 -0
  43. package/dist/core/derive.d.ts +61 -0
  44. package/dist/core/derive.js +69 -0
  45. package/dist/core/derive.js.map +1 -0
  46. package/dist/core/extension-points.d.ts +16 -0
  47. package/dist/core/feature-context.d.ts +5 -1
  48. package/dist/core/feature-context.js +10 -3
  49. package/dist/core/feature-context.js.map +1 -1
  50. package/dist/core/feature.d.ts +41 -19
  51. package/dist/core/feature.js.map +1 -1
  52. package/dist/core/fences.js +1 -1
  53. package/dist/core/fences.js.map +1 -1
  54. package/dist/core/index.d.ts +6 -2
  55. package/dist/core/index.js +5 -1
  56. package/dist/core/pipeline.d.ts +14 -5
  57. package/dist/core/pipeline.js +19 -7
  58. package/dist/core/pipeline.js.map +1 -1
  59. package/dist/core/properties.d.ts +22 -0
  60. package/dist/core/properties.js +17 -0
  61. package/dist/core/properties.js.map +1 -0
  62. package/dist/core/schema/block-ids.js +5 -5
  63. package/dist/core/schema/block-ids.js.map +1 -1
  64. package/dist/core/schema/index.d.ts +2 -1
  65. package/dist/core/schema/index.js +2 -1
  66. package/dist/core/schema/index.js.map +1 -1
  67. package/dist/core/schema/opaque-fence.d.ts +4 -4
  68. package/dist/core/schema/opaque-fence.js +7 -7
  69. package/dist/core/schema/opaque-fence.js.map +1 -1
  70. package/dist/core/schema/persisted-names.d.ts +58 -0
  71. package/dist/core/schema/persisted-names.js +96 -0
  72. package/dist/core/schema/persisted-names.js.map +1 -0
  73. package/dist/core/toolbar-items.js +1 -1
  74. package/dist/core/toolbar-items.js.map +1 -1
  75. package/dist/core/util.js +5 -1
  76. package/dist/core/util.js.map +1 -1
  77. package/dist/document/DocumentSurface.vue.d.ts +3 -3
  78. package/dist/features/ai/agent-ops.js +1 -1
  79. package/dist/features/ai/ai-review.js +1 -1
  80. package/dist/features/ai/assistant.d.ts +4 -4
  81. package/dist/features/ai/assistant.js +1 -1
  82. package/dist/features/ai/assistant.js.map +1 -1
  83. package/dist/features/ai/components/AiSelectionMenu.js +1 -1
  84. package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
  85. package/dist/features/ai/components/ModelQuickSwitch.js +7 -7
  86. package/dist/features/ai/components/ModelQuickSwitch.js.map +1 -1
  87. package/dist/features/ai/components/ModelQuickSwitch.vue.d.ts +4 -4
  88. package/dist/features/ai/components/SettingsAiTab.js +8 -8
  89. package/dist/features/ai/components/SettingsAiTab.js.map +1 -1
  90. package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
  91. package/dist/features/ai/components/SettingsModelProfiles.js.map +1 -1
  92. package/dist/features/ai/document-chat-model.d.ts +2 -2
  93. package/dist/features/ai/document-chat-model.js +5 -5
  94. package/dist/features/ai/document-chat-model.js.map +1 -1
  95. package/dist/features/ai/document-chat.js +2 -2
  96. package/dist/features/ai/document-chat.js.map +1 -1
  97. package/dist/features/ai/feature.d.ts +2 -2
  98. package/dist/features/ai/feature.js +2 -2
  99. package/dist/features/ai/feature.js.map +1 -1
  100. package/dist/features/ai/ghost-autocomplete.d.ts +4 -4
  101. package/dist/features/ai/ghost-autocomplete.js +1 -1
  102. package/dist/features/ai/ghost-autocomplete.js.map +1 -1
  103. package/dist/features/ai/index.d.ts +3 -3
  104. package/dist/features/ai/index.js +2 -2
  105. package/dist/features/ai/model-profiles.d.ts +9 -9
  106. package/dist/features/ai/model-profiles.js +13 -13
  107. package/dist/features/ai/model-profiles.js.map +1 -1
  108. package/dist/features/ai/protocol/agent-ops.d.ts +2 -2
  109. package/dist/features/ai/protocol/agent-ops.js +1 -1
  110. package/dist/features/ai/protocol/agent-ops.js.map +1 -1
  111. package/dist/features/ai/protocol/block-lines.d.ts +7 -7
  112. package/dist/features/ai/protocol/block-lines.js +10 -10
  113. package/dist/features/ai/protocol/block-lines.js.map +1 -1
  114. package/dist/features/ai/protocol/chat.d.ts +5 -5
  115. package/dist/features/ai/protocol/chat.js +12 -12
  116. package/dist/features/ai/protocol/chat.js.map +1 -1
  117. package/dist/features/ai/protocol/generate.d.ts +1 -1
  118. package/dist/features/ai/protocol/generate.js +1 -1
  119. package/dist/features/ai/protocol/generate.js.map +1 -1
  120. package/dist/features/ai/protocol/index.d.ts +3 -3
  121. package/dist/features/ai/protocol/index.js +2 -2
  122. package/dist/features/ai/protocol/models.d.ts +3 -3
  123. package/dist/features/ai/protocol/models.js +3 -3
  124. package/dist/features/ai/protocol/models.js.map +1 -1
  125. package/dist/features/ai/protocol/schemas.d.ts +1 -1
  126. package/dist/features/ai/protocol/schemas.js +1 -1
  127. package/dist/features/ai/protocol/schemas.js.map +1 -1
  128. package/dist/features/ai/protocol/tasks.d.ts +5 -5
  129. package/dist/features/ai/protocol/tasks.js +12 -12
  130. package/dist/features/ai/protocol/tasks.js.map +1 -1
  131. package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
  132. package/dist/features/ai/react/ModelQuickSwitch.d.ts +4 -4
  133. package/dist/features/ai/react/ModelQuickSwitch.js +6 -6
  134. package/dist/features/ai/react/ModelQuickSwitch.js.map +1 -1
  135. package/dist/features/ai/react/SettingsAiTab.d.ts +1 -1
  136. package/dist/features/ai/react/SettingsAiTab.js +8 -8
  137. package/dist/features/ai/react/SettingsAiTab.js.map +1 -1
  138. package/dist/features/ai/react/SettingsModelProfiles.d.ts +1 -1
  139. package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
  140. package/dist/features/ai/react/SettingsModelProfiles.js.map +1 -1
  141. package/dist/features/ai/react/use-ai-assistant.d.ts +1 -1
  142. package/dist/features/ai/react/use-ai-assistant.js +1 -1
  143. package/dist/features/ai/react/use-ai-assistant.js.map +1 -1
  144. package/dist/features/ai/react/use-model-profiles.d.ts +5 -5
  145. package/dist/features/ai/react/use-model-profiles.js +2 -2
  146. package/dist/features/ai/react/use-model-profiles.js.map +1 -1
  147. package/dist/features/ai/use-ai-assistant.d.ts +1 -1
  148. package/dist/features/ai/use-ai-assistant.js +1 -1
  149. package/dist/features/ai/use-ai-assistant.js.map +1 -1
  150. package/dist/features/ai/use-model-profiles.d.ts +4 -4
  151. package/dist/features/ai/use-model-profiles.js +2 -2
  152. package/dist/features/ai/use-model-profiles.js.map +1 -1
  153. package/dist/features/block-ui/block-actions.d.ts +8 -4
  154. package/dist/features/block-ui/block-actions.js +38 -22
  155. package/dist/features/block-ui/block-actions.js.map +1 -1
  156. package/dist/features/block-ui/block-target.d.ts +29 -0
  157. package/dist/features/block-ui/block-target.js +63 -0
  158. package/dist/features/block-ui/block-target.js.map +1 -0
  159. package/dist/features/block-ui/components/BlockHandles.js +3 -3
  160. package/dist/features/block-ui/components/BlockHandles.js.map +1 -1
  161. package/dist/features/block-ui/components/BlockUi.js +11 -23
  162. package/dist/features/block-ui/components/BlockUi.js.map +1 -1
  163. package/dist/features/block-ui/feature.d.ts +3 -2
  164. package/dist/features/block-ui/feature.js +3 -2
  165. package/dist/features/block-ui/feature.js.map +1 -1
  166. package/dist/features/block-ui/handle-geometry.js +12 -23
  167. package/dist/features/block-ui/handle-geometry.js.map +1 -1
  168. package/dist/features/block-ui/index.d.ts +2 -1
  169. package/dist/features/block-ui/index.js +2 -1
  170. package/dist/features/block-ui/react/BlockHandles.d.ts +1 -1
  171. package/dist/features/block-ui/react/BlockHandles.js +4 -4
  172. package/dist/features/block-ui/react/BlockHandles.js.map +1 -1
  173. package/dist/features/block-ui/react/BlockUi.js +13 -25
  174. package/dist/features/block-ui/react/BlockUi.js.map +1 -1
  175. package/dist/features/comments/model.js +1 -1
  176. package/dist/features/composites/accepted-children.d.ts +5 -4
  177. package/dist/features/composites/accepted-children.js +26 -26
  178. package/dist/features/composites/accepted-children.js.map +1 -1
  179. package/dist/features/composites/ai-brief.d.ts +4 -4
  180. package/dist/features/composites/ai-brief.js +14 -12
  181. package/dist/features/composites/ai-brief.js.map +1 -1
  182. package/dist/features/composites/anchors.d.ts +1 -1
  183. package/dist/features/composites/anchors.js.map +1 -1
  184. package/dist/features/composites/baskets.d.ts +1 -1
  185. package/dist/features/composites/baskets.js.map +1 -1
  186. package/dist/features/composites/bibliography-command.d.ts +1 -1
  187. package/dist/features/composites/bibliography-form.js +1 -1
  188. package/dist/features/composites/bibliography-form.js.map +1 -1
  189. package/dist/features/composites/bibliography.js.map +1 -1
  190. package/dist/features/composites/block-types-json.d.ts +38 -0
  191. package/dist/features/composites/block-types-json.js +122 -0
  192. package/dist/features/composites/block-types-json.js.map +1 -0
  193. package/dist/features/composites/block-types.d.ts +119 -0
  194. package/dist/features/composites/block-types.js +343 -0
  195. package/dist/features/composites/block-types.js.map +1 -0
  196. package/dist/features/composites/capabilities.js +109 -0
  197. package/dist/features/composites/capabilities.js.map +1 -0
  198. package/dist/features/composites/components/BibliographyEditor.js +5 -5
  199. package/dist/features/composites/components/BibliographyEditor.js.map +1 -1
  200. package/dist/features/composites/components/CompositeBasketMenu.js +2 -2
  201. package/dist/features/composites/components/CompositeBasketMenu.js.map +1 -1
  202. package/dist/features/composites/components/CompositeChrome.js +18 -8
  203. package/dist/features/composites/components/CompositeChrome.js.map +1 -1
  204. package/dist/features/composites/components/CompositeChrome.vue.d.ts +1 -1
  205. package/dist/features/composites/components/CompositeDetails.js +11 -10
  206. package/dist/features/composites/components/CompositeDetails.js.map +1 -1
  207. package/dist/features/composites/components/CompositeDetails.vue.d.ts +1 -1
  208. package/dist/features/composites/components/CompositeFieldEditor.js +7 -7
  209. package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
  210. package/dist/features/composites/components/CompositeFieldEditor.vue.d.ts +5 -5
  211. package/dist/features/composites/components/CompositeInspector.js +136 -113
  212. package/dist/features/composites/components/CompositeInspector.js.map +1 -1
  213. package/dist/features/composites/components/CompositeInspector.vue.d.ts +3 -3
  214. package/dist/features/composites/components/CompositeStatusMenu.js +2 -2
  215. package/dist/features/composites/components/CompositeStatusMenu.js.map +1 -1
  216. package/dist/features/composites/components/CompositeSummaryEditor.js +1 -1
  217. package/dist/features/composites/components/CompositeSummaryEditor.js.map +1 -1
  218. package/dist/features/composites/components/CompositeSummaryEditor.vue.d.ts +1 -1
  219. package/dist/features/composites/components/CompositeTable.js +2 -2
  220. package/dist/features/composites/components/CompositeTable.js.map +1 -1
  221. package/dist/features/composites/components/CompositeTagsEditor.js +1 -1
  222. package/dist/features/composites/components/CompositeTagsEditor.js.map +1 -1
  223. package/dist/features/composites/components/CompositeTagsEditor.vue.d.ts +1 -1
  224. package/dist/features/composites/components/CompositeView.js +19 -17
  225. package/dist/features/composites/components/CompositeView.js.map +1 -1
  226. package/dist/features/composites/components/DerivationBadge.js +15 -6
  227. package/dist/features/composites/components/DerivationBadge.js.map +1 -1
  228. package/dist/features/composites/components/FocusChip.js +1 -1
  229. package/dist/features/composites/components/FocusChip.js.map +1 -1
  230. package/dist/features/composites/components/FocusChip.vue.d.ts +1 -1
  231. package/dist/features/composites/components/ReferenceFieldSelect.js +3 -3
  232. package/dist/features/composites/components/ReferenceFieldSelect.js.map +1 -1
  233. package/dist/features/composites/components/ReferenceView.js +1 -1
  234. package/dist/features/composites/components/ReferenceView.js.map +1 -1
  235. package/dist/features/composites/components/SettingsEditingTab.js +18 -18
  236. package/dist/features/composites/components/SettingsEditingTab.js.map +1 -1
  237. package/dist/features/composites/components/SettingsEditingTab.vue.d.ts +3 -3
  238. package/dist/features/composites/convert.d.ts +4 -4
  239. package/dist/features/composites/convert.js +27 -28
  240. package/dist/features/composites/convert.js.map +1 -1
  241. package/dist/features/composites/derived-chrome.js +34 -28
  242. package/dist/features/composites/derived-chrome.js.map +1 -1
  243. package/dist/features/composites/editing-settings-keys.js +6 -6
  244. package/dist/features/composites/editing-settings-keys.js.map +1 -1
  245. package/dist/features/composites/feature.d.ts +10 -10
  246. package/dist/features/composites/feature.js +58 -46
  247. package/dist/features/composites/feature.js.map +1 -1
  248. package/dist/features/composites/fences.d.ts +3 -3
  249. package/dist/features/composites/fences.js +18 -13
  250. package/dist/features/composites/fences.js.map +1 -1
  251. package/dist/features/composites/focus.d.ts +10 -10
  252. package/dist/features/composites/focus.js +11 -11
  253. package/dist/features/composites/focus.js.map +1 -1
  254. package/dist/features/composites/heading-composites.d.ts +5 -5
  255. package/dist/features/composites/heading-composites.js +13 -16
  256. package/dist/features/composites/heading-composites.js.map +1 -1
  257. package/dist/features/composites/index.d.ts +20 -18
  258. package/dist/features/composites/index.js +9 -7
  259. package/dist/features/composites/inspector-model.d.ts +45 -22
  260. package/dist/features/composites/inspector-model.js +53 -14
  261. package/dist/features/composites/inspector-model.js.map +1 -1
  262. package/dist/features/composites/legacy.d.ts +5 -4
  263. package/dist/features/composites/legacy.js +16 -23
  264. package/dist/features/composites/legacy.js.map +1 -1
  265. package/dist/features/composites/markdown-export.js +3 -3
  266. package/dist/features/composites/markdown-export.js.map +1 -1
  267. package/dist/features/composites/merge.d.ts +8 -7
  268. package/dist/features/composites/merge.js +39 -12
  269. package/dist/features/composites/merge.js.map +1 -1
  270. package/dist/features/composites/meta-display.js +5 -5
  271. package/dist/features/composites/meta-display.js.map +1 -1
  272. package/dist/features/composites/meta-keys.d.ts +5 -5
  273. package/dist/features/composites/meta-keys.js +8 -8
  274. package/dist/features/composites/meta-keys.js.map +1 -1
  275. package/dist/features/composites/node-views.d.ts +3 -3
  276. package/dist/features/composites/node-views.js +2 -2
  277. package/dist/features/composites/node-views.js.map +1 -1
  278. package/dist/features/composites/nodes.d.ts +13 -13
  279. package/dist/features/composites/nodes.js +100 -77
  280. package/dist/features/composites/nodes.js.map +1 -1
  281. package/dist/features/composites/outline.d.ts +15 -12
  282. package/dist/features/composites/outline.js +24 -21
  283. package/dist/features/composites/outline.js.map +1 -1
  284. package/dist/features/composites/properties.d.ts +51 -0
  285. package/dist/features/composites/properties.js +89 -0
  286. package/dist/features/composites/properties.js.map +1 -0
  287. package/dist/features/composites/react/BibliographyEditor.js +5 -5
  288. package/dist/features/composites/react/BibliographyEditor.js.map +1 -1
  289. package/dist/features/composites/react/CompositeBasketMenu.js +2 -2
  290. package/dist/features/composites/react/CompositeBasketMenu.js.map +1 -1
  291. package/dist/features/composites/react/CompositeChrome.d.ts +1 -1
  292. package/dist/features/composites/react/CompositeChrome.js +15 -6
  293. package/dist/features/composites/react/CompositeChrome.js.map +1 -1
  294. package/dist/features/composites/react/CompositeDetails.d.ts +1 -1
  295. package/dist/features/composites/react/CompositeDetails.js +11 -10
  296. package/dist/features/composites/react/CompositeDetails.js.map +1 -1
  297. package/dist/features/composites/react/CompositeFieldEditor.d.ts +5 -5
  298. package/dist/features/composites/react/CompositeFieldEditor.js +6 -6
  299. package/dist/features/composites/react/CompositeFieldEditor.js.map +1 -1
  300. package/dist/features/composites/react/CompositeInspector.d.ts +3 -3
  301. package/dist/features/composites/react/CompositeInspector.js +142 -128
  302. package/dist/features/composites/react/CompositeInspector.js.map +1 -1
  303. package/dist/features/composites/react/CompositeStatusMenu.js +2 -2
  304. package/dist/features/composites/react/CompositeStatusMenu.js.map +1 -1
  305. package/dist/features/composites/react/CompositeSummaryEditor.d.ts +1 -1
  306. package/dist/features/composites/react/CompositeSummaryEditor.js +1 -1
  307. package/dist/features/composites/react/CompositeSummaryEditor.js.map +1 -1
  308. package/dist/features/composites/react/CompositeTable.d.ts +1 -1
  309. package/dist/features/composites/react/CompositeTable.js +3 -3
  310. package/dist/features/composites/react/CompositeTable.js.map +1 -1
  311. package/dist/features/composites/react/CompositeTagsEditor.d.ts +1 -1
  312. package/dist/features/composites/react/CompositeTagsEditor.js +1 -1
  313. package/dist/features/composites/react/CompositeTagsEditor.js.map +1 -1
  314. package/dist/features/composites/react/CompositeView.js +17 -15
  315. package/dist/features/composites/react/CompositeView.js.map +1 -1
  316. package/dist/features/composites/react/DerivationBadge.js +13 -4
  317. package/dist/features/composites/react/DerivationBadge.js.map +1 -1
  318. package/dist/features/composites/react/FocusChip.d.ts +1 -1
  319. package/dist/features/composites/react/FocusChip.js +1 -1
  320. package/dist/features/composites/react/FocusChip.js.map +1 -1
  321. package/dist/features/composites/react/ReferenceFieldSelect.js +3 -3
  322. package/dist/features/composites/react/ReferenceFieldSelect.js.map +1 -1
  323. package/dist/features/composites/react/ReferenceView.d.ts +1 -1
  324. package/dist/features/composites/react/ReferenceView.js +1 -1
  325. package/dist/features/composites/react/ReferenceView.js.map +1 -1
  326. package/dist/features/composites/react/SettingsEditingTab.d.ts +4 -4
  327. package/dist/features/composites/react/SettingsEditingTab.js +20 -20
  328. package/dist/features/composites/react/SettingsEditingTab.js.map +1 -1
  329. package/dist/features/composites/react.js +1 -1
  330. package/dist/features/composites/react.js.map +1 -1
  331. package/dist/features/composites/reference-ai.js +1 -1
  332. package/dist/features/composites/reference-ai.js.map +1 -1
  333. package/dist/features/composites/reference-label.d.ts +3 -3
  334. package/dist/features/composites/reference-label.js +6 -6
  335. package/dist/features/composites/reference-label.js.map +1 -1
  336. package/dist/features/composites/reference-suggestion.d.ts +1 -1
  337. package/dist/features/composites/reference-suggestion.js +3 -3
  338. package/dist/features/composites/reference-suggestion.js.map +1 -1
  339. package/dist/features/composites/reference-text.d.ts +2 -2
  340. package/dist/features/composites/reference-text.js +4 -4
  341. package/dist/features/composites/reference-text.js.map +1 -1
  342. package/dist/features/composites/reference.d.ts +3 -3
  343. package/dist/features/composites/reference.js +1 -1
  344. package/dist/features/composites/reference.js.map +1 -1
  345. package/dist/features/composites/reveal-source.d.ts +3 -3
  346. package/dist/features/composites/reveal-source.js +6 -6
  347. package/dist/features/composites/reveal-source.js.map +1 -1
  348. package/dist/features/composites/rows-model.d.ts +2 -2
  349. package/dist/features/composites/rows-model.js +4 -3
  350. package/dist/features/composites/rows-model.js.map +1 -1
  351. package/dist/features/composites/rows.d.ts +16 -28
  352. package/dist/features/composites/rows.js +20 -54
  353. package/dist/features/composites/rows.js.map +1 -1
  354. package/dist/features/composites/schema.d.ts +12 -11
  355. package/dist/features/composites/schema.js +10 -9
  356. package/dist/features/composites/schema.js.map +1 -1
  357. package/dist/features/composites/source.d.ts +20 -0
  358. package/dist/features/composites/source.js +40 -0
  359. package/dist/features/composites/source.js.map +1 -0
  360. package/dist/features/composites/split.d.ts +11 -11
  361. package/dist/features/composites/split.js +20 -20
  362. package/dist/features/composites/split.js.map +1 -1
  363. package/dist/features/composites/structure-mode.js +3 -3
  364. package/dist/features/composites/structure-mode.js.map +1 -1
  365. package/dist/features/composites/table-model.js +2 -2
  366. package/dist/features/composites/table-model.js.map +1 -1
  367. package/dist/features/composites/use-composite-inspector.js +3 -1
  368. package/dist/features/composites/use-composite-inspector.js.map +1 -1
  369. package/dist/features/composites/use-composite-rows.d.ts +2 -2
  370. package/dist/features/composites/use-composite-rows.js.map +1 -1
  371. package/dist/features/composites/vue.js +1 -1
  372. package/dist/features/composites/vue.js.map +1 -1
  373. package/dist/features/image-studio/components/ImageStudioDialog.js +1 -1
  374. package/dist/features/image-studio/components/ImageStudioDialog.js.map +1 -1
  375. package/dist/features/image-studio/components/ImageStudioDialog.vue.d.ts +1 -1
  376. package/dist/features/image-studio/components/ImageStudioView.js.map +1 -1
  377. package/dist/features/image-studio/feature.d.ts +1 -1
  378. package/dist/features/image-studio/feature.js +1 -1
  379. package/dist/features/image-studio/feature.js.map +1 -1
  380. package/dist/features/image-studio/node-core.d.ts +3 -3
  381. package/dist/features/image-studio/node-core.js +1 -1
  382. package/dist/features/image-studio/node-core.js.map +1 -1
  383. package/dist/features/image-studio/node-store.d.ts +1 -1
  384. package/dist/features/image-studio/node-store.js +1 -1
  385. package/dist/features/image-studio/node-store.js.map +1 -1
  386. package/dist/features/image-studio/react/ImageStudioDialog.d.ts +1 -1
  387. package/dist/features/image-studio/react/ImageStudioDialog.js +1 -1
  388. package/dist/features/image-studio/react/ImageStudioDialog.js.map +1 -1
  389. package/dist/features/image-studio/react/ImageStudioView.js +1 -1
  390. package/dist/features/image-studio/react/ImageStudioView.js.map +1 -1
  391. package/dist/features/image-studio/react/store.d.ts +3 -3
  392. package/dist/features/image-studio/react/store.js.map +1 -1
  393. package/dist/features/image-studio/react.d.ts +1 -1
  394. package/dist/features/image-studio/react.js +1 -1
  395. package/dist/features/image-studio/react.js.map +1 -1
  396. package/dist/features/image-studio/schema.d.ts +1 -1
  397. package/dist/features/image-studio/schema.js +1 -1
  398. package/dist/features/image-studio/schema.js.map +1 -1
  399. package/dist/features/image-studio/state.d.ts +3 -3
  400. package/dist/features/image-studio/state.js +2 -2
  401. package/dist/features/image-studio/state.js.map +1 -1
  402. package/dist/features/image-studio/store.d.ts +3 -3
  403. package/dist/features/image-studio/vue.d.ts +1 -1
  404. package/dist/features/image-studio/vue.js +1 -1
  405. package/dist/features/image-studio/vue.js.map +1 -1
  406. package/dist/features/media/index.js +1 -1
  407. package/dist/features/media/react/AudioRecorderModal.js +1 -1
  408. package/dist/features/media/react.js +1 -1
  409. package/dist/features/mentions/tag.js +2 -1
  410. package/dist/features/mentions/tag.js.map +1 -1
  411. package/dist/features/themes/components/SettingsAppearanceTab.js +1 -1
  412. package/dist/features/themes/components/SettingsAppearanceTab.js.map +1 -1
  413. package/dist/features/themes/feature.d.ts +1 -1
  414. package/dist/features/themes/feature.js.map +1 -1
  415. package/dist/features/themes/react/SettingsAppearanceTab.d.ts +1 -1
  416. package/dist/features/themes/react/SettingsAppearanceTab.js +1 -1
  417. package/dist/features/themes/react/SettingsAppearanceTab.js.map +1 -1
  418. package/dist/features/themes/theme-runtime.js +1 -1
  419. package/dist/features/themes/theme-runtime.js.map +1 -1
  420. package/dist/features/transcript/TranscriptSegmentView.js +1 -1
  421. package/dist/features/transcript/TranscriptSegmentView.js.map +1 -1
  422. package/dist/features/transcript/react/TranscriptSegmentView.js +1 -1
  423. package/dist/features/transcript/react/TranscriptSegmentView.js.map +1 -1
  424. package/dist/features/transcript/segment-node.js.map +1 -1
  425. package/dist/features/transfer/feature.js +1 -1
  426. package/dist/features/transfer/feature.js.map +1 -1
  427. package/dist/features/transfer/transforms.d.ts +4 -4
  428. package/dist/features/transfer/transforms.js +12 -12
  429. package/dist/features/transfer/transforms.js.map +1 -1
  430. package/dist/i18n/de/ai.js +4 -4
  431. package/dist/i18n/de/ai.js.map +1 -1
  432. package/dist/i18n/de/authors.js +1 -1
  433. package/dist/i18n/de/authors.js.map +1 -1
  434. package/dist/i18n/de/comments.js +1 -1
  435. package/dist/i18n/de/comments.js.map +1 -1
  436. package/dist/i18n/de/composites.js +62 -59
  437. package/dist/i18n/de/composites.js.map +1 -1
  438. package/dist/i18n/de/image-studio.js +2 -2
  439. package/dist/i18n/de/image-studio.js.map +1 -1
  440. package/dist/i18n/de/integrations.js +20 -20
  441. package/dist/i18n/de/integrations.js.map +1 -1
  442. package/dist/i18n/de/markdown.js +1 -1
  443. package/dist/i18n/de/markdown.js.map +1 -1
  444. package/dist/i18n/de/palette.js +1 -1
  445. package/dist/i18n/de/palette.js.map +1 -1
  446. package/dist/i18n/de/themes.js +3 -3
  447. package/dist/i18n/de/themes.js.map +1 -1
  448. package/dist/i18n/de/transfer.js +2 -2
  449. package/dist/i18n/de/transfer.js.map +1 -1
  450. package/dist/i18n/de/tree.js +6 -6
  451. package/dist/i18n/de/tree.js.map +1 -1
  452. package/dist/i18n/de.d.ts +101 -98
  453. package/dist/i18n/en/ai.js +2 -2
  454. package/dist/i18n/en/ai.js.map +1 -1
  455. package/dist/i18n/en/composites.js +64 -61
  456. package/dist/i18n/en/composites.js.map +1 -1
  457. package/dist/i18n/en/integrations.js +9 -9
  458. package/dist/i18n/en/integrations.js.map +1 -1
  459. package/dist/i18n/en/markdown.js +1 -1
  460. package/dist/i18n/en/markdown.js.map +1 -1
  461. package/dist/i18n/en/palette.js +1 -1
  462. package/dist/i18n/en/palette.js.map +1 -1
  463. package/dist/i18n/en/themes.js +4 -4
  464. package/dist/i18n/en/themes.js.map +1 -1
  465. package/dist/i18n/en/tree.js +5 -5
  466. package/dist/i18n/en/tree.js.map +1 -1
  467. package/dist/index.d.ts +4 -1
  468. package/dist/integrations/composite-ai/bibliography-fetch.d.ts +6 -6
  469. package/dist/integrations/composite-ai/bibliography-fetch.js +19 -19
  470. package/dist/integrations/composite-ai/bibliography-fetch.js.map +1 -1
  471. package/dist/integrations/composite-ai/composite-fill.d.ts +4 -4
  472. package/dist/integrations/composite-ai/composite-fill.js +5 -5
  473. package/dist/integrations/composite-ai/composite-fill.js.map +1 -1
  474. package/dist/integrations/composite-ai/composite-summary.d.ts +2 -2
  475. package/dist/integrations/composite-ai/composite-summary.js +5 -5
  476. package/dist/integrations/composite-ai/composite-summary.js.map +1 -1
  477. package/dist/integrations/composite-ai/derive.d.ts +17 -17
  478. package/dist/integrations/composite-ai/derive.js +27 -26
  479. package/dist/integrations/composite-ai/derive.js.map +1 -1
  480. package/dist/integrations/composite-ai/index.d.ts +14 -15
  481. package/dist/integrations/composite-ai/index.js +98 -77
  482. package/dist/integrations/composite-ai/index.js.map +1 -1
  483. package/dist/integrations/composite-ai/outline-sanitize.d.ts +1 -1
  484. package/dist/integrations/composite-ai/outline-sanitize.js +1 -1
  485. package/dist/integrations/composite-ai/outline-sanitize.js.map +1 -1
  486. package/dist/integrations/composite-ai/rendition.d.ts +9 -9
  487. package/dist/integrations/composite-ai/rendition.js +27 -27
  488. package/dist/integrations/composite-ai/rendition.js.map +1 -1
  489. package/dist/integrations/composite-ai/split.d.ts +8 -8
  490. package/dist/integrations/composite-ai/split.js +12 -12
  491. package/dist/integrations/composite-ai/split.js.map +1 -1
  492. package/dist/integrations/composite-ai/transfer-transforms.d.ts +2 -2
  493. package/dist/integrations/composite-ai/transfer-transforms.js +12 -12
  494. package/dist/integrations/composite-ai/transfer-transforms.js.map +1 -1
  495. package/dist/integrations/composite-media/index.d.ts +4 -3
  496. package/dist/integrations/composite-media/index.js +20 -14
  497. package/dist/integrations/composite-media/index.js.map +1 -1
  498. package/dist/markdown/MarkdownSurface.vue.d.ts +7 -7
  499. package/dist/markdown/document.js +1 -1
  500. package/dist/markdown/document.js.map +1 -1
  501. package/dist/nuxt/module.d.ts +30 -24
  502. package/dist/nuxt/module.js.map +1 -1
  503. package/dist/nuxt/runtime/server/plugins/editor-server.js +3 -2
  504. package/dist/nuxt/runtime/server/plugins/editor-server.js.map +1 -1
  505. package/dist/presets/compose.d.ts +6 -6
  506. package/dist/presets/compose.js +8 -5
  507. package/dist/presets/compose.js.map +1 -1
  508. package/dist/react/AdvancedEditor.d.ts +2 -2
  509. package/dist/react/AdvancedEditor.js +1 -1
  510. package/dist/react/AdvancedEditor.js.map +1 -1
  511. package/dist/react/compose.d.ts +1 -1
  512. package/dist/react/compose.js +2 -2
  513. package/dist/react/compose.js.map +1 -1
  514. package/dist/react/composed-surface.d.ts +5 -5
  515. package/dist/react/composed-surface.js +2 -2
  516. package/dist/react/composed-surface.js.map +1 -1
  517. package/dist/server/ai.d.ts +7 -7
  518. package/dist/server/ai.js +1 -1
  519. package/dist/server/ai.js.map +1 -1
  520. package/dist/server/auth.d.ts +1 -1
  521. package/dist/server/collab-state.d.ts +1 -1
  522. package/dist/server/collab-state.js.map +1 -1
  523. package/dist/server/collab.d.ts +4 -4
  524. package/dist/server/collab.js +22 -17
  525. package/dist/server/collab.js.map +1 -1
  526. package/dist/server/handlers/ai.d.ts +9 -6
  527. package/dist/server/handlers/ai.js +13 -17
  528. package/dist/server/handlers/ai.js.map +1 -1
  529. package/dist/server/handlers/context.d.ts +4 -0
  530. package/dist/server/handlers/context.js.map +1 -1
  531. package/dist/server/handlers/editor.d.ts +33 -32
  532. package/dist/server/handlers/editor.js +13 -8
  533. package/dist/server/handlers/editor.js.map +1 -1
  534. package/dist/server/handlers/images.d.ts +1 -1
  535. package/dist/server/handlers/images.js +1 -1
  536. package/dist/server/handlers/images.js.map +1 -1
  537. package/dist/server/host.d.ts +4 -0
  538. package/dist/server/host.js +10 -2
  539. package/dist/server/host.js.map +1 -1
  540. package/dist/server/index.d.ts +3 -3
  541. package/dist/server/ports.d.ts +8 -8
  542. package/dist/server/ports.js.map +1 -1
  543. package/dist/settings/index.d.ts +20 -19
  544. package/dist/settings/index.js +25 -24
  545. package/dist/settings/index.js.map +1 -1
  546. package/dist/settings/migrate.d.ts +18 -0
  547. package/dist/settings/migrate.js +46 -0
  548. package/dist/settings/migrate.js.map +1 -0
  549. package/dist/settings/values.d.ts +12 -12
  550. package/dist/settings/values.js +11 -11
  551. package/dist/settings/values.js.map +1 -1
  552. package/dist/styles/composites.css +10 -10
  553. package/dist/tree/DocumentTree.js +12 -5
  554. package/dist/tree/DocumentTree.js.map +1 -1
  555. package/dist/tree/drop-rules.js +5 -5
  556. package/dist/tree/drop-rules.js.map +1 -1
  557. package/dist/tree/react/DocumentTree.js +14 -5
  558. package/dist/tree/react/DocumentTree.js.map +1 -1
  559. package/dist/tree/tree-host.js +6 -6
  560. package/dist/tree/tree-host.js.map +1 -1
  561. package/dist/tree/tree-items.js +34 -28
  562. package/dist/tree/tree-items.js.map +1 -1
  563. package/dist/tree/tree-model.js +16 -16
  564. package/dist/tree/tree-model.js.map +1 -1
  565. package/dist/tree/tree-rows.js +2 -2
  566. package/dist/tree/tree-rows.js.map +1 -1
  567. package/dist/tree/tree-view-model.d.ts +2 -2
  568. package/dist/tree/tree-view-model.js.map +1 -1
  569. package/dist/vue/AdvancedEditor.js +2 -2
  570. package/dist/vue/AdvancedEditor.js.map +1 -1
  571. package/dist/vue/AdvancedEditor.vue.d.ts +10 -10
  572. package/dist/vue/SimpleEditor.vue.d.ts +4 -4
  573. package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
  574. package/dist/vue/compose.d.ts +1 -1
  575. package/dist/vue/compose.js +2 -2
  576. package/dist/vue/compose.js.map +1 -1
  577. package/package.json +1 -1
  578. package/dist/features/composites/derivation.d.ts +0 -62
  579. package/dist/features/composites/derivation.js +0 -102
  580. package/dist/features/composites/derivation.js.map +0 -1
  581. package/dist/features/composites/kinds.d.ts +0 -83
  582. package/dist/features/composites/kinds.js +0 -285
  583. package/dist/features/composites/kinds.js.map +0 -1
@@ -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/** 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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"}
@@ -8,16 +8,16 @@ import { Ref } from "vue";
8
8
  *
9
9
  * The dialog applies the reducers of `./state` and hands the result to `update`; the store only persists it. The
10
10
  * store's methods never touch `state` themselves:
11
- * - `generate` produces one image from the block's `state.description`, the active Prompt-Bausteine and the active
11
+ * - `generate` produces one image from the block's `state.description`, the active prompt blocks and the active
12
12
  * images (the host builds the prompt). When it resolves, the dialog calls `update(addGeneration(state, image, turn))`;
13
13
  * when it rejects (and the signal was not aborted) the dialog appends a turn with `imageId: null` and the message as
14
14
  * `problem`. An aborted generation adds nothing.
15
15
  * - `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 Prompt-Bausteine could not be loaded, say); the dialog shows it as a notice.
16
+ * - `problem` is a problem of the store itself (the prompt blocks could not be loaded, say); the dialog shows it as a notice.
17
17
  */
18
18
  export interface ImageStudioStore {
19
19
  readonly state: Ref<ImageStudioState>;
20
- /** The managed list of Prompt-Bausteine, loaded by the host. */
20
+ /** The managed list of prompt blocks, loaded by the host. */
21
21
  readonly promptBlocks: Ref<readonly ImageStudioPromptBlock[]>;
22
22
  /** Called with the state after a reducer; the host persists it. */
23
23
  update: (next: ImageStudioState) => void;
@@ -8,7 +8,7 @@ import { ImageStudioNodeStore, ImageStudioNodeStoreOptions, createImageStudioNod
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 Prompt-Bausteine need a backend with the `images` capability; without it the view
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
  export declare function imageStudioFeature(): VueFeature;
@@ -8,7 +8,7 @@ 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 Prompt-Bausteine need a backend with the `images` capability; without it the view
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() {
@@ -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 Prompt-Bausteine 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":";;;;;;;;;;;;;AAgCA,SAAgB,qBAAiC;CAC/C,OAAO,uBAAuB,EAAE,UAAU,oBAAoBA,WAAe,EAAE,CAAC;AAClF"}
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 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":";;;;;;;;;;;;;AAgCA,SAAgB,qBAAiC;CAC/C,OAAO,uBAAuB,EAAE,UAAU,oBAAoBA,WAAe,EAAE,CAAC;AAClF"}
@@ -1,7 +1,7 @@
1
1
  import { MAX_MEDIA_BYTES, MEDIA_TYPES, acceptFor, mediaKindOf, mediaMime } from "./schema.js";
2
- import { createAudioRecorder } from "./audio-recorder.js";
3
2
  import { AUDIO_RECORDER_OPEN, Media, fileNodeFor, formatBytes, mediaNodeFor, openAudioRecorder, pickFiles, uploadOf } from "./upload.js";
4
3
  import { MAX_TEXT_BYTES, fileImport, isTextFile } from "./file-import.js";
5
4
  import { mediaBlocks, playerOptions } from "./node-views.js";
6
5
  import { mediaFeatureCore } from "./feature.js";
6
+ import { createAudioRecorder } from "./audio-recorder.js";
7
7
  export { AUDIO_RECORDER_OPEN, MAX_MEDIA_BYTES, MAX_TEXT_BYTES, MEDIA_TYPES, Media, acceptFor, createAudioRecorder, fileImport, fileNodeFor, formatBytes, isTextFile, mediaBlocks, mediaFeatureCore, mediaKindOf, mediaMime, mediaNodeFor, openAudioRecorder, pickFiles, playerOptions, uploadOf };
@@ -5,8 +5,8 @@ import { cn } from "../../../react/ui/utils.js";
5
5
  import { Icon } from "../../../react/Icon.js";
6
6
  import { Button } from "../../../react/ui/button.js";
7
7
  import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "../../../react/ui/dialog.js";
8
- import "../upload.js";
9
8
  import { useNotifier } from "../../../react/use-notifier.js";
9
+ import "../upload.js";
10
10
  import { useAudioRecorder } from "./use-audio-recorder.js";
11
11
  import { useCallback, useEffect, useRef, useState } from "react";
12
12
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,8 +1,8 @@
1
1
  import { useEditorI18n } from "../../react/use-editor-i18n.js";
2
+ import { useNotifier } from "../../react/use-notifier.js";
2
3
  import { fileImport } from "./file-import.js";
3
4
  import { playerOptions } from "./node-views.js";
4
5
  import { mediaFeatureCore } from "./feature.js";
5
- import { useNotifier } from "../../react/use-notifier.js";
6
6
  import { useAudioRecorder } from "./react/use-audio-recorder.js";
7
7
  import { AudioRecorderModal } from "./react/AudioRecorderModal.js";
8
8
  import { AudioView } from "./react/AudioView.js";
@@ -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 === "compositeTags" && Array.isArray(content.attrs?.tags)) {
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":";;;;AAWA,MAAa,WAAW;AACxB,MAAM,iBAAiB;;;;;AAMvB,SAAgB,iBAAiB,KAAqB;CACpD,OAAO,IAAI,KAAK,CAAC,CAAC,YAAY,CAAC,CAC5B,QAAQ,QAAQ,GAAG,CAAC,CACpB,QAAQ,qBAAqB,EAAE,CAAC,CAChC,QAAQ,UAAU,GAAG,CAAC,CACtB,QAAQ,YAAY,EAAE,CAAC,CACvB,MAAM,GAAG,cAAc;AAC5B;;AAGA,SAAgB,gBAAgB,SAAyC,MAAgC;CACvG,IAAI,CAAC,SAAS,OAAO;CACrB,IAAI,QAAQ,SAAA,SAAqB,OAAO,QAAQ,OAAO,SAAS,YAAY,QAAQ,MAAM,MAAM,KAAK,IAAI,QAAQ,MAAM,IAAI;CAC3H,IAAI,QAAQ,SAAS,mBAAmB,MAAM,QAAQ,QAAQ,OAAO,IAAI,GAClE;OAAA,MAAM,QAAQ,QAAQ,MAAM,MAAM,IAAI,OAAO,SAAS,YAAY,MAAM,KAAK,IAAI,IAAI;CAAA;CAE5F,KAAK,MAAM,SAAS,QAAQ,WAAW,CAAC,GAAG,gBAAgB,OAAO,IAAI;CACtE,OAAO;AACT;;;;;;;AAQA,SAAgB,aAAa,MAAmB,OAAyC;CACvF,IAAI,CAAC,MAAM,MAAM,OAAO;CAExB,MAAM,QAAQ;CAEd,MAAM,aAAa,SAAqC;EACtD,MAAM,OAAO,KAAK,QAAQ;EAC1B,IAAI,CAAC,KAAK,SAAS,GAAG,KAAK,KAAK,OAAO,MAAK,MAAK,EAAE,SAAS,UAAU,EAAE,SAAS,MAAM,GAAG,OAAO,CAAC,IAAI;EACtG,MAAM,MAAqB,CAAC;EAC5B,IAAI,OAAO;EACX,KAAK,MAAM,SAAS,KAAK,SAAS,KAAK,GAAG;GACxC,MAAM,OAAO,MAAM,EAAE,CAAE,YAAY;GACnC,IAAI,CAAC,MAAM,IAAI,IAAI,GAAG;GACtB,MAAM,KAAK,MAAM,SAAS;GAC1B,IAAI,KAAK,MAAM,IAAI,KAAK;IAAE,GAAG;IAAM,MAAM,KAAK,MAAM,MAAM,EAAE;GAAE,CAAC;GAC/D,IAAI,KAAK;IAAE,MAAA;IAAgB,OAAO,EAAE,KAAK;IAAG,GAAI,KAAK,OAAO,SAAS,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;GAAG,CAAC;GAClG,OAAO,KAAK,MAAM,EAAE,CAAC;EACvB;EACA,IAAI,CAAC,IAAI,QAAQ,OAAO,CAAC,IAAI;EAC7B,IAAI,OAAO,KAAK,QAAQ,IAAI,KAAK;GAAE,GAAG;GAAM,MAAM,KAAK,MAAM,IAAI;EAAE,CAAC;EACpE,OAAO;CACT;CAEA,MAAM,QAAQ,SAAmC;EAC/C,IAAI,CAAC,KAAK,WAAW,KAAK,SAAS,aAAa,OAAO;EACvD,IAAI,UAAU;EACd,MAAM,UAAU,KAAK,QAAQ,SAAS,UAAU;GAC9C,MAAM,OAAO,MAAM,SAAS,SAAS,UAAU,KAAK,IAAI,CAAC,KAAK,KAAK,CAAC;GACpE,IAAI,KAAK,WAAW,KAAK,KAAK,OAAO,OAAO,UAAU;GACtD,OAAO;EACT,CAAC;EACD,OAAO,UAAU;GAAE,GAAG;GAAM;EAAQ,IAAI;CAC1C;CACA,OAAO,KAAK,IAAI;AAClB;;AAWA,MAAa,yBAAyB,IAAI,UAAU,eAAe;;;;;AAMnE,MAAa,UAAU,KAAK,OAAmB;CAC7C,MAAA;CACA,OAAO;CACP,QAAQ;CACR,MAAM;CACN,YAAY;CAEZ,UAAU;CAEV,aAAa;EACX,OAAO;GAAE,gBAAgB,EAAE,OAAO,MAAM;GAAG,YAAY;GAAM,UAAU;EAAK;CAC9E;CAEA,gBAAgB;EACd,OAAO,EACL,MAAM;GACJ,SAAS;GACT,YAAW,OAAM,iBAAiB,GAAG,aAAa,WAAW,KAAK,GAAG,eAAe,EAAE;GACtF,aAAY,WAAU,EAAE,aAAa,MAAM,KAAK;EAClD,EACF;CACF;CAEA,YAAY;EACV,OAAO,CAAC,EAAE,KAAK,mBAAmB,KAAK,KAAK,IAAI,CAAC;CACnD;CAEA,WAAW,EAAE,MAAM,kBAAkB;EACnC,OAAO;GAAC;GAAQ,gBAAgB,EAAE,aAAa,KAAK,KAAK,GAAG,KAAK,QAAQ,gBAAgB,cAAc;GAAG,IAAI,KAAK,MAAM;EAAM;CACjI;CAEA,aAAa,EAAE,WAAW,IAAI,KAAK,MAAM;CAGzC,iBAAgB,SAAQ,IAAI,KAAK,OAAO,QAAQ;CAEhD,uBAAuB;EACrB,OAAO,EAEL,iBAAiB,KAAK,OAAO,SAAS,SAAS,EAAE,IAAI,YAAY;GAC/D,MAAM,EAAE,OAAO,YAAY,MAAM;GACjC,MAAM,SAAS,QAAQ;GACvB,IAAI,CAAC,SAAS,QAAQ,KAAK,SAAS,KAAK,MAAM,OAAO;GACtD,GAAG,WAAW,IAAI,OAAO,MAAM,QAAQ,QAAQ,MAAM,OAAO,UAAU,QAAQ,GAAG;GACjF,OAAO;EACT,CAAC,EACH;CACF;CAEA,wBAAwB;EACtB,MAAM,EAAE,eAAe,KAAK;EAC5B,IAAI,CAAC,YAAY,OAAO,CAAC;EACzB,MAAM,EAAE,OAAO,GAAG,SAAS;EAE3B,MAAM,gBAA2C,EAAE,OAAO,YAAY,CAAC,SAAS,KAAK,MAAM,IAAI,YAAY,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC;EACpI,OAAO,CAAC,WAAwC;GAC9C,WAAW;GACX,MAAM;GACN,aAAa;GACb,QAAO,MAAK,aAAa,CAAC,MAAM,QAAQ,CAAC,KAAK;GAC9C,UAAU,EAAE,QAAQ,OAAO,YAAY;IACrC,MAAM,OAAO,iBAAiB,MAAM,IAAI;IACxC,IAAI,CAAC,MAAM;IAGX,IADkB,OAAO,KAAK,MAAM,UAAU,IAAI,WACnC,MAAM,WAAW,GAAG,GAAG,MAAM,MAAM;IAClD,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CACnB,gBAAgB,OAAO,CAAC;KAAE,MAAM,KAAK;KAAM,OAAO,EAAE,KAAK;IAAE,GAAG;KAAE,MAAM;KAAQ,MAAM;IAAI,CAAC,CAAC,CAAC,CAC3F,IAAI;IACP,OAAO,KAAK,IAAI,cAAc,aAAa,aAAa,CAAC,EAAE,cAAc;IACzE,IAAK,MAA4B,OAAO,KAAK,QAAQ,WAAW,QAAQ,IAAI;GAC9E;GACA,GAAG;GACH,QAAQ,KAAK;EACf,CAAC,CAAC;CACJ;AACF,CAAC"}
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
- /** „Darstellung“: the Obsidian theme of the whole app (`appearance.theme`); „Standard“ is the Nuxt UI look. */
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 „Darstellung“ settings tab component of the framework. */
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 „Darstellung“ 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
+ {"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
- /** „Darstellung“: the Obsidian theme of the whole app (`appearance.theme`); „Standard“ is the normal look. */
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
- /** „Darstellung“: the Obsidian theme of the whole app (`appearance.theme`); „Standard“ is the normal look. */
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, die Standard-Darstellung wird verwendet`, error);
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, die Standard-Darstellung wird verwendet`, 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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"}
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 Baustein (a no-op without the composites feature or a source). */
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();
@@ -1 +1 @@
1
- {"version":3,"file":"TranscriptSegmentView.js","names":["selected","node","_openBlock","_createBlock","_unref","_normalizeClass","_withCtx","_createElementVNode","_createElementBlock","_withModifiers","_toDisplayString","_createCommentVNode","SuggestionList","_createVNode"],"sources":["../../../src/features/transcript/TranscriptSegmentView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { NodeViewContent, NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3'\nimport { computed, onBeforeUnmount, ref, useTemplateRef, watch } from 'vue'\nimport { METADATA_LIST, type MetadataItem, metadataOf } from '../../core'\nimport SuggestionList from '../../vue/components/SuggestionList.vue'\nimport { useEditorI18n } from '../../vue/use-editor-i18n'\nimport { formatSegmentTime } from './segment-node'\n\nconst props = defineProps(nodeViewProps)\nconst { t } = useEditorI18n()\n\nconst speaker = computed(() => String(props.node.attrs.speaker ?? ''))\n// Typed text is a draft until committed (blur, Enter, picking a suggestion): one undo step per name, and the\n// speaker list is only extended with whole names, not with every prefix typed on the way.\nconst draft = ref(speaker.value)\nconst focused = ref(false)\nwatch(speaker, (value) => {\n if (!focused.value) draft.value = value\n})\n// `editor.isEditable` is not reactive, so a read-only editor is detected when the input gets focus.\nconst readonly = ref(false)\n\nconst open = ref(false)\nconst items = ref<MetadataItem[]>([])\nconst list = useTemplateRef<InstanceType<typeof SuggestionList>>('list')\n\n// Same loading as the composite tags: 150 ms after typing pauses, a newer search aborts the older one (sources may be remote).\nlet timer: ReturnType<typeof setTimeout> | undefined\nlet pending: AbortController | undefined\nfunction scheduleSearch(query: string) {\n clearTimeout(timer)\n timer = setTimeout(async () => {\n pending?.abort()\n const controller = pending = new AbortController()\n try {\n const found = await metadataOf(props.editor).search(METADATA_LIST.speakers, { query, editor: props.editor, signal: controller.signal })\n if (!controller.signal.aborted) items.value = found.filter(item => item.label.trim())\n }\n catch (error) {\n if (!controller.signal.aborted) console.warn('Sprecher-Vorschläge konnten nicht geladen werden', error)\n }\n }, 150)\n}\nonBeforeUnmount(() => {\n clearTimeout(timer)\n pending?.abort()\n})\n\nfunction openList(query: string) {\n if (!props.editor.isEditable) return\n open.value = true\n scheduleSearch(query)\n}\n\nfunction closeList() {\n open.value = false\n clearTimeout(timer)\n pending?.abort()\n}\n\nfunction onFocus() {\n focused.value = true\n readonly.value = !props.editor.isEditable\n}\n\nfunction onInput(e: Event) {\n draft.value = (e.target as HTMLInputElement).value\n openList(draft.value)\n}\n\n/** Writes the draft to the node; a name the speaker list does not know yet is added to it (reported via `metadataCreate`). */\nasync function commit(name = draft.value.trim()) {\n draft.value = name\n if (readonly.value || name === speaker.value) return\n // Searched before the node changes: `search` calls the source synchronously, so a list derived from the document\n // (the default `speakers`) does not already contain the name just typed here.\n const found = name ? metadataOf(props.editor).search(METADATA_LIST.speakers, { query: name, editor: props.editor }) : null\n props.updateAttributes({ speaker: name })\n if (!found) return\n if (!(await found).some(item => item.label.toLowerCase() === name.toLowerCase())) {\n metadataOf(props.editor).create(METADATA_LIST.speakers, { id: name, label: name })\n }\n}\n\nfunction pick(item: unknown) {\n closeList()\n void commit((item as MetadataItem).label)\n}\n\nfunction onBlur() {\n focused.value = false\n closeList()\n void commit()\n}\n\n/** The speaker field of the segment `step` places before/after this one (document order), if any. */\nfunction neighbourField(input: HTMLInputElement, step: -1 | 1): HTMLInputElement | undefined {\n const fields = [...props.editor.view.dom.querySelectorAll<HTMLInputElement>('.transcript-segment__speaker input')]\n return fields[fields.indexOf(input) + step]\n}\n\n// An open list takes ArrowUp/ArrowDown/Enter/Tab; otherwise ArrowUp/ArrowDown move to the neighbouring segment's\n// speaker and Enter continues in this segment's text.\nfunction onKeyDown(e: KeyboardEvent) {\n if (open.value && items.value.length && list.value?.onKeyDown(e)) {\n e.preventDefault()\n return\n }\n const input = e.target as HTMLInputElement\n if (e.key === 'Escape' && open.value) {\n e.preventDefault()\n closeList()\n }\n else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n const next = neighbourField(input, e.key === 'ArrowUp' ? -1 : 1)\n if (!next) return\n e.preventDefault()\n next.focus()\n next.setSelectionRange(next.value.length, next.value.length)\n }\n else if (e.key === 'Enter') {\n e.preventDefault()\n input.blur()\n props.editor.commands.focus(props.getPos()! + props.node.nodeSize - 1)\n }\n}\n\n/** Seeks and plays the source recording of the enclosing Baustein (a no-op without the composites feature or a source). */\nfunction seek() {\n const start = props.node.attrs.start\n const pos = props.getPos()\n // `seekSource` is contributed by the composites feature; without it the editor has no command and the click does nothing.\n if (typeof start === 'number' && pos !== undefined) props.editor.commands.seekSource?.(pos, start)\n}\n</script>\n\n<template>\n <NodeViewWrapper\n :class=\"['transcript-segment my-2', { 'is-selected rounded-md ring-1 ring-[var(--sk-primary)]': selected }]\"\n data-type=\"transcript-segment\"\n :data-id=\"node.attrs.id || undefined\"\n >\n <div class=\"grid grid-cols-[5.5rem_1fr] items-start gap-2\">\n <div class=\"transcript-segment__speaker relative pt-0.5\" contenteditable=\"false\">\n <input\n :value=\"draft\"\n :title=\"draft || undefined\"\n :placeholder=\"t('transcript.speaker')\"\n :aria-label=\"t('transcript.speaker')\"\n autocomplete=\"off\"\n role=\"combobox\"\n :aria-expanded=\"open && items.length > 0\"\n :readonly=\"readonly\"\n class=\"w-full truncate rounded bg-transparent px-1 text-sm text-[var(--sk-muted)] outline-none placeholder:text-[var(--sk-fg-dimmed)] focus:bg-[color-mix(in_oklab,var(--sk-bg-accented)_50%,transparent)] focus:text-[var(--sk-fg-highlighted)]\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n @mousedown=\"openList('')\"\n @input=\"onInput\"\n @keydown=\"onKeyDown\"\n >\n <button\n v-if=\"node.attrs.start != null\"\n type=\"button\"\n class=\"transcript-segment__time block rounded px-1 text-xs tabular-nums text-[var(--sk-muted)] hover:text-[var(--sk-primary)] hover:underline\"\n :title=\"t('transcript.seek', { time: formatSegmentTime(node.attrs.start) })\"\n :aria-label=\"t('transcript.seek', { time: formatSegmentTime(node.attrs.start) })\"\n @mousedown.prevent\n @click=\"seek\"\n >\n {{ formatSegmentTime(node.attrs.start) }}\n </button>\n <SuggestionList\n v-if=\"open && items.length\"\n ref=\"list\"\n class=\"absolute top-full left-0 z-50 mt-1\"\n :items=\"items\"\n :display=\"item => ({ label: (item as MetadataItem).label })\"\n :command=\"pick\"\n :loading=\"false\"\n loading-text=\"\"\n empty-text=\"\"\n />\n </div>\n <NodeViewContent class=\"transcript-segment__text\" />\n </div>\n </NodeViewWrapper>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"TranscriptSegmentView.js","names":["selected","node","_openBlock","_createBlock","_unref","_normalizeClass","_withCtx","_createElementVNode","_createElementBlock","_withModifiers","_toDisplayString","_createCommentVNode","SuggestionList","_createVNode"],"sources":["../../../src/features/transcript/TranscriptSegmentView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { NodeViewContent, NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3'\nimport { computed, onBeforeUnmount, ref, useTemplateRef, watch } from 'vue'\nimport { METADATA_LIST, type MetadataItem, metadataOf } from '../../core'\nimport SuggestionList from '../../vue/components/SuggestionList.vue'\nimport { useEditorI18n } from '../../vue/use-editor-i18n'\nimport { formatSegmentTime } from './segment-node'\n\nconst props = defineProps(nodeViewProps)\nconst { t } = useEditorI18n()\n\nconst speaker = computed(() => String(props.node.attrs.speaker ?? ''))\n// Typed text is a draft until committed (blur, Enter, picking a suggestion): one undo step per name, and the\n// speaker list is only extended with whole names, not with every prefix typed on the way.\nconst draft = ref(speaker.value)\nconst focused = ref(false)\nwatch(speaker, (value) => {\n if (!focused.value) draft.value = value\n})\n// `editor.isEditable` is not reactive, so a read-only editor is detected when the input gets focus.\nconst readonly = ref(false)\n\nconst open = ref(false)\nconst items = ref<MetadataItem[]>([])\nconst list = useTemplateRef<InstanceType<typeof SuggestionList>>('list')\n\n// Same loading as the composite tags: 150 ms after typing pauses, a newer search aborts the older one (sources may be remote).\nlet timer: ReturnType<typeof setTimeout> | undefined\nlet pending: AbortController | undefined\nfunction scheduleSearch(query: string) {\n clearTimeout(timer)\n timer = setTimeout(async () => {\n pending?.abort()\n const controller = pending = new AbortController()\n try {\n const found = await metadataOf(props.editor).search(METADATA_LIST.speakers, { query, editor: props.editor, signal: controller.signal })\n if (!controller.signal.aborted) items.value = found.filter(item => item.label.trim())\n }\n catch (error) {\n if (!controller.signal.aborted) console.warn('Sprecher-Vorschläge konnten nicht geladen werden', error)\n }\n }, 150)\n}\nonBeforeUnmount(() => {\n clearTimeout(timer)\n pending?.abort()\n})\n\nfunction openList(query: string) {\n if (!props.editor.isEditable) return\n open.value = true\n scheduleSearch(query)\n}\n\nfunction closeList() {\n open.value = false\n clearTimeout(timer)\n pending?.abort()\n}\n\nfunction onFocus() {\n focused.value = true\n readonly.value = !props.editor.isEditable\n}\n\nfunction onInput(e: Event) {\n draft.value = (e.target as HTMLInputElement).value\n openList(draft.value)\n}\n\n/** Writes the draft to the node; a name the speaker list does not know yet is added to it (reported via `metadataCreate`). */\nasync function commit(name = draft.value.trim()) {\n draft.value = name\n if (readonly.value || name === speaker.value) return\n // Searched before the node changes: `search` calls the source synchronously, so a list derived from the document\n // (the default `speakers`) does not already contain the name just typed here.\n const found = name ? metadataOf(props.editor).search(METADATA_LIST.speakers, { query: name, editor: props.editor }) : null\n props.updateAttributes({ speaker: name })\n if (!found) return\n if (!(await found).some(item => item.label.toLowerCase() === name.toLowerCase())) {\n metadataOf(props.editor).create(METADATA_LIST.speakers, { id: name, label: name })\n }\n}\n\nfunction pick(item: unknown) {\n closeList()\n void commit((item as MetadataItem).label)\n}\n\nfunction onBlur() {\n focused.value = false\n closeList()\n void commit()\n}\n\n/** The speaker field of the segment `step` places before/after this one (document order), if any. */\nfunction neighbourField(input: HTMLInputElement, step: -1 | 1): HTMLInputElement | undefined {\n const fields = [...props.editor.view.dom.querySelectorAll<HTMLInputElement>('.transcript-segment__speaker input')]\n return fields[fields.indexOf(input) + step]\n}\n\n// An open list takes ArrowUp/ArrowDown/Enter/Tab; otherwise ArrowUp/ArrowDown move to the neighbouring segment's\n// speaker and Enter continues in this segment's text.\nfunction onKeyDown(e: KeyboardEvent) {\n if (open.value && items.value.length && list.value?.onKeyDown(e)) {\n e.preventDefault()\n return\n }\n const input = e.target as HTMLInputElement\n if (e.key === 'Escape' && open.value) {\n e.preventDefault()\n closeList()\n }\n else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n const next = neighbourField(input, e.key === 'ArrowUp' ? -1 : 1)\n if (!next) return\n e.preventDefault()\n next.focus()\n next.setSelectionRange(next.value.length, next.value.length)\n }\n else if (e.key === 'Enter') {\n e.preventDefault()\n input.blur()\n props.editor.commands.focus(props.getPos()! + props.node.nodeSize - 1)\n }\n}\n\n/** Seeks and plays the source recording of the enclosing Typed block (a no-op without the composites feature or a source). */\nfunction seek() {\n const start = props.node.attrs.start\n const pos = props.getPos()\n // `seekSource` is contributed by the composites feature; without it the editor has no command and the click does nothing.\n if (typeof start === 'number' && pos !== undefined) props.editor.commands.seekSource?.(pos, start)\n}\n</script>\n\n<template>\n <NodeViewWrapper\n :class=\"['transcript-segment my-2', { 'is-selected rounded-md ring-1 ring-[var(--sk-primary)]': selected }]\"\n data-type=\"transcript-segment\"\n :data-id=\"node.attrs.id || undefined\"\n >\n <div class=\"grid grid-cols-[5.5rem_1fr] items-start gap-2\">\n <div class=\"transcript-segment__speaker relative pt-0.5\" contenteditable=\"false\">\n <input\n :value=\"draft\"\n :title=\"draft || undefined\"\n :placeholder=\"t('transcript.speaker')\"\n :aria-label=\"t('transcript.speaker')\"\n autocomplete=\"off\"\n role=\"combobox\"\n :aria-expanded=\"open && items.length > 0\"\n :readonly=\"readonly\"\n class=\"w-full truncate rounded bg-transparent px-1 text-sm text-[var(--sk-muted)] outline-none placeholder:text-[var(--sk-fg-dimmed)] focus:bg-[color-mix(in_oklab,var(--sk-bg-accented)_50%,transparent)] focus:text-[var(--sk-fg-highlighted)]\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n @mousedown=\"openList('')\"\n @input=\"onInput\"\n @keydown=\"onKeyDown\"\n >\n <button\n v-if=\"node.attrs.start != null\"\n type=\"button\"\n class=\"transcript-segment__time block rounded px-1 text-xs tabular-nums text-[var(--sk-muted)] hover:text-[var(--sk-primary)] hover:underline\"\n :title=\"t('transcript.seek', { time: formatSegmentTime(node.attrs.start) })\"\n :aria-label=\"t('transcript.seek', { time: formatSegmentTime(node.attrs.start) })\"\n @mousedown.prevent\n @click=\"seek\"\n >\n {{ formatSegmentTime(node.attrs.start) }}\n </button>\n <SuggestionList\n v-if=\"open && items.length\"\n ref=\"list\"\n class=\"absolute top-full left-0 z-50 mt-1\"\n :items=\"items\"\n :display=\"item => ({ label: (item as MetadataItem).label })\"\n :command=\"pick\"\n :loading=\"false\"\n loading-text=\"\"\n empty-text=\"\"\n />\n </div>\n <NodeViewContent class=\"transcript-segment__text\" />\n </div>\n </NodeViewWrapper>\n</template>\n"],"mappings":"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"}
@@ -100,7 +100,7 @@ function TranscriptSegmentView({ node, editor, selected, updateAttributes, getPo
100
100
  editor.commands.focus(getPos() + node.nodeSize - 1);
101
101
  }
102
102
  };
103
- /** Seeks and plays the source recording of the enclosing Baustein (a no-op without the composites feature or a source). */
103
+ /** Seeks and plays the source recording of the enclosing Typed block (a no-op without the composites feature or a source). */
104
104
  const seek = () => {
105
105
  const start = node.attrs.start;
106
106
  const pos = getPos();