@ecsplico/editor 2.1.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (701) hide show
  1. package/CHANGELOG.md +74 -1
  2. package/README.md +39 -156
  3. package/dist/adapters/comments/drizzle.d.ts +1 -1
  4. package/dist/adapters/comments/{client.js → rest-model.js} +3 -3
  5. package/dist/adapters/comments/rest-model.js.map +1 -0
  6. package/dist/adapters/comments/types.d.ts +2 -2
  7. package/dist/adapters/document-state.d.ts +23 -0
  8. package/dist/adapters/document-state.js +66 -0
  9. package/dist/adapters/document-state.js.map +1 -0
  10. package/dist/adapters/documents.js +14 -11
  11. package/dist/adapters/documents.js.map +1 -1
  12. package/dist/adapters/drizzle.d.ts +2 -2
  13. package/dist/adapters/drizzle.js +97 -88
  14. package/dist/adapters/drizzle.js.map +1 -1
  15. package/dist/adapters/forge.d.ts +12 -7
  16. package/dist/adapters/forge.js +38 -45
  17. package/dist/adapters/forge.js.map +1 -1
  18. package/dist/adapters/http.d.ts +20 -0
  19. package/dist/adapters/http.js +68 -0
  20. package/dist/adapters/http.js.map +1 -0
  21. package/dist/adapters/index.d.ts +5 -5
  22. package/dist/adapters/json.d.ts +3 -3
  23. package/dist/adapters/json.js +58 -49
  24. package/dist/adapters/json.js.map +1 -1
  25. package/dist/adapters/memory.d.ts +21 -0
  26. package/dist/adapters/memory.js +53 -0
  27. package/dist/adapters/memory.js.map +1 -0
  28. package/dist/adapters/nitro.d.ts +23 -20
  29. package/dist/adapters/nitro.js +58 -163
  30. package/dist/adapters/nitro.js.map +1 -1
  31. package/dist/adapters/react.d.ts +55 -0
  32. package/dist/adapters/react.js +209 -0
  33. package/dist/adapters/react.js.map +1 -0
  34. package/dist/adapters/shared.d.ts +15 -21
  35. package/dist/adapters/shared.js +10 -8
  36. package/dist/adapters/shared.js.map +1 -1
  37. package/dist/adapters/vue.d.ts +15 -30
  38. package/dist/adapters/vue.js +36 -87
  39. package/dist/adapters/vue.js.map +1 -1
  40. package/dist/code/CodeSurface.vue.d.ts +3 -3
  41. package/dist/config/index.d.ts +105 -0
  42. package/dist/config/index.js +95 -0
  43. package/dist/config/index.js.map +1 -0
  44. package/dist/core/abilities.js +1 -0
  45. package/dist/core/abilities.js.map +1 -1
  46. package/dist/{adapters/routes.js → core/api-routes.js} +5 -3
  47. package/dist/core/api-routes.js.map +1 -0
  48. package/dist/core/backend-capability.d.ts +7 -0
  49. package/dist/core/backend-capability.js +17 -0
  50. package/dist/core/backend-capability.js.map +1 -0
  51. package/dist/core/backend-types.d.ts +245 -0
  52. package/dist/core/backend-types.js +17 -0
  53. package/dist/core/backend-types.js.map +1 -0
  54. package/dist/core/backend.d.ts +108 -0
  55. package/dist/core/backend.js +14 -0
  56. package/dist/core/backend.js.map +1 -0
  57. package/dist/core/block-convert.d.ts +7 -5
  58. package/dist/core/block-convert.js +28 -5
  59. package/dist/core/block-convert.js.map +1 -1
  60. package/dist/core/command-types.d.ts +89 -0
  61. package/dist/core/commands.d.ts +40 -0
  62. package/dist/core/commands.js +151 -0
  63. package/dist/core/commands.js.map +1 -0
  64. package/dist/core/context.d.ts +17 -20
  65. package/dist/core/context.js +24 -21
  66. package/dist/core/context.js.map +1 -1
  67. package/dist/core/core-feature.d.ts +22 -7
  68. package/dist/core/core-feature.js +51 -18
  69. package/dist/core/core-feature.js.map +1 -1
  70. package/dist/core/define-editor.d.ts +4 -3
  71. package/dist/core/define-editor.js +14 -7
  72. package/dist/core/define-editor.js.map +1 -1
  73. package/dist/core/document-markdown.js +12 -3
  74. package/dist/core/document-markdown.js.map +1 -1
  75. package/dist/core/editor-ui.d.ts +8 -1
  76. package/dist/core/editor-ui.js +9 -8
  77. package/dist/core/editor-ui.js.map +1 -1
  78. package/dist/core/extension-points.d.ts +3 -15
  79. package/dist/core/feature-context.d.ts +8 -7
  80. package/dist/core/feature-context.js +12 -11
  81. package/dist/core/feature-context.js.map +1 -1
  82. package/dist/core/feature.d.ts +35 -19
  83. package/dist/core/feature.js.map +1 -1
  84. package/dist/core/fences.d.ts +2 -1
  85. package/dist/core/fences.js +3 -2
  86. package/dist/core/fences.js.map +1 -1
  87. package/dist/core/fetch-error.d.ts +1 -1
  88. package/dist/core/fetch-error.js +3 -1
  89. package/dist/core/fetch-error.js.map +1 -1
  90. package/dist/core/hash.d.ts +5 -0
  91. package/dist/core/hash.js +11 -0
  92. package/dist/core/hash.js.map +1 -0
  93. package/dist/core/http-backend.d.ts +11 -0
  94. package/dist/core/http-backend.js +195 -0
  95. package/dist/core/http-backend.js.map +1 -0
  96. package/dist/core/index.d.ts +14 -9
  97. package/dist/core/index.js +12 -9
  98. package/dist/core/palette.d.ts +48 -0
  99. package/dist/core/palette.js +95 -0
  100. package/dist/core/palette.js.map +1 -0
  101. package/dist/core/schema/abilities.d.ts +2 -1
  102. package/dist/core/schema/abilities.js +5 -1
  103. package/dist/core/schema/abilities.js.map +1 -1
  104. package/dist/core/schema/index.d.ts +11 -5
  105. package/dist/core/schema/index.js +13 -8
  106. package/dist/core/schema/index.js.map +1 -1
  107. package/dist/core/schema/mark-flags.d.ts +8 -1
  108. package/dist/core/schema/of-features.d.ts +13 -0
  109. package/dist/core/schema/of-features.js +14 -0
  110. package/dist/core/schema/of-features.js.map +1 -0
  111. package/dist/core/slash.d.ts +8 -6
  112. package/dist/core/slash.js +24 -16
  113. package/dist/core/slash.js.map +1 -1
  114. package/dist/core/surface-session.js +13 -5
  115. package/dist/core/surface-session.js.map +1 -1
  116. package/dist/core/toolbar-items.d.ts +10 -4
  117. package/dist/core/toolbar-items.js +269 -195
  118. package/dist/core/toolbar-items.js.map +1 -1
  119. package/dist/core/util.js +11 -0
  120. package/dist/core/util.js.map +1 -0
  121. package/dist/document/DocumentSurface.d.ts +1 -1
  122. package/dist/document/DocumentSurface.vue.d.ts +3 -3
  123. package/dist/features/ai/agent-op-review.d.ts +1 -1
  124. package/dist/features/ai/agent-op-review.js +1 -1
  125. package/dist/features/ai/agent-ops.js +1 -1
  126. package/dist/features/ai/ai-apply.d.ts +2 -2
  127. package/dist/features/ai/ai-apply.js +1 -1
  128. package/dist/features/ai/ai-review.js +1 -1
  129. package/dist/features/ai/ai-sse.d.ts +6 -6
  130. package/dist/features/ai/ai-sse.js +20 -63
  131. package/dist/features/ai/ai-sse.js.map +1 -1
  132. package/dist/features/ai/assistant.d.ts +8 -7
  133. package/dist/features/ai/assistant.js +3 -3
  134. package/dist/features/ai/assistant.js.map +1 -1
  135. package/dist/features/ai/block-actions.d.ts +3 -2
  136. package/dist/features/ai/block-actions.js +3 -2
  137. package/dist/features/ai/block-actions.js.map +1 -1
  138. package/dist/features/ai/components/AiSelectionMenu.js +1 -1
  139. package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
  140. package/dist/features/ai/components/DocumentChatOps.vue.d.ts +1 -1
  141. package/dist/features/ai/components/SettingsModelProfiles.js +8 -8
  142. package/dist/features/ai/components/SettingsModelProfiles.js.map +1 -1
  143. package/dist/features/ai/document-chat-model.d.ts +8 -7
  144. package/dist/features/ai/document-chat-model.js +5 -5
  145. package/dist/features/ai/document-chat-model.js.map +1 -1
  146. package/dist/features/ai/document-chat.js +1 -1
  147. package/dist/features/ai/feature.js +27 -14
  148. package/dist/features/ai/feature.js.map +1 -1
  149. package/dist/features/ai/ghost-autocomplete.d.ts +7 -6
  150. package/dist/features/ai/ghost-autocomplete.js +6 -9
  151. package/dist/features/ai/ghost-autocomplete.js.map +1 -1
  152. package/dist/features/ai/index.d.ts +4 -3
  153. package/dist/features/ai/index.js +10 -10
  154. package/dist/features/ai/model-profiles.d.ts +5 -4
  155. package/dist/features/ai/model-profiles.js +2 -2
  156. package/dist/features/ai/model-profiles.js.map +1 -1
  157. package/dist/features/ai/protocol/block-lines.js +1 -1
  158. package/dist/features/ai/protocol/block-lines.js.map +1 -1
  159. package/dist/features/ai/protocol/chat.js +4 -1
  160. package/dist/features/ai/protocol/chat.js.map +1 -1
  161. package/dist/features/ai/protocol/index.d.ts +3 -2
  162. package/dist/features/ai/protocol/models.d.ts +2 -16
  163. package/dist/features/ai/protocol/models.js.map +1 -1
  164. package/dist/features/ai/rate-limit.d.ts +1 -1
  165. package/dist/features/ai/rate-limit.js +3 -1
  166. package/dist/features/ai/rate-limit.js.map +1 -1
  167. package/dist/features/ai/react/AiSelectionMenu.js +2 -2
  168. package/dist/features/ai/react/AiSidebar.js +4 -4
  169. package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
  170. package/dist/features/ai/react/DocumentChatOps.d.ts +1 -1
  171. package/dist/features/ai/react/DocumentChatOps.js +1 -1
  172. package/dist/features/ai/react/SettingsModelProfiles.js +8 -10
  173. package/dist/features/ai/react/SettingsModelProfiles.js.map +1 -1
  174. package/dist/features/ai/react/use-ai-assistant.d.ts +1 -1
  175. package/dist/features/ai/react/use-ai-assistant.js +3 -3
  176. package/dist/features/ai/react/use-ai-assistant.js.map +1 -1
  177. package/dist/features/ai/react/use-document-chat.js +2 -2
  178. package/dist/features/ai/react/use-document-chat.js.map +1 -1
  179. package/dist/features/ai/react/use-model-profiles.d.ts +2 -1
  180. package/dist/features/ai/selection-flow.d.ts +1 -1
  181. package/dist/features/ai/selection-flow.js +1 -1
  182. package/dist/features/ai/sidebar-flow.d.ts +1 -1
  183. package/dist/features/ai/sidebar-flow.js +1 -1
  184. package/dist/features/ai/use-ai-assistant.d.ts +1 -1
  185. package/dist/features/ai/use-ai-assistant.js +3 -3
  186. package/dist/features/ai/use-ai-assistant.js.map +1 -1
  187. package/dist/features/ai/use-document-chat.js +2 -2
  188. package/dist/features/ai/use-document-chat.js.map +1 -1
  189. package/dist/features/ai/use-model-profiles.d.ts +2 -1
  190. package/dist/features/authors/attribution.d.ts +1 -1
  191. package/dist/features/authors/feature.js +12 -11
  192. package/dist/features/authors/feature.js.map +1 -1
  193. package/dist/features/block-ui/block-actions.d.ts +22 -1
  194. package/dist/features/block-ui/block-actions.js +26 -2
  195. package/dist/features/block-ui/block-actions.js.map +1 -1
  196. package/dist/features/block-ui/components/BlockActionsOverlay.js +33 -4
  197. package/dist/features/block-ui/components/BlockActionsOverlay.js.map +1 -1
  198. package/dist/features/block-ui/components/BlockActionsOverlay.vue.d.ts +4 -0
  199. package/dist/features/block-ui/components/BlockUi.js +1 -1
  200. package/dist/features/block-ui/feature.js +1 -0
  201. package/dist/features/block-ui/feature.js.map +1 -1
  202. package/dist/features/block-ui/index.d.ts +2 -2
  203. package/dist/features/block-ui/index.js +2 -2
  204. package/dist/features/block-ui/react/BlockActionsOverlay.d.ts +4 -0
  205. package/dist/features/block-ui/react/BlockActionsOverlay.js +38 -12
  206. package/dist/features/block-ui/react/BlockActionsOverlay.js.map +1 -1
  207. package/dist/features/block-ui/react/BlockUi.js +1 -1
  208. package/dist/features/collab/anchor-resolver.js +1 -1
  209. package/dist/features/collab/feature.js +2 -0
  210. package/dist/features/collab/feature.js.map +1 -1
  211. package/dist/features/comments/components/CommentThreadView.vue.d.ts +2 -1
  212. package/dist/features/comments/feature.js +12 -11
  213. package/dist/features/comments/feature.js.map +1 -1
  214. package/dist/features/comments/index.d.ts +3 -2
  215. package/dist/features/comments/index.js +1 -1
  216. package/dist/features/comments/margin-layout.d.ts +3 -2
  217. package/dist/features/comments/model.d.ts +3 -67
  218. package/dist/features/comments/model.js +2 -2
  219. package/dist/features/comments/model.js.map +1 -1
  220. package/dist/features/comments/react/CommentThreadView.d.ts +3 -2
  221. package/dist/features/comments/react/use-comments.d.ts +2 -1
  222. package/dist/features/comments/react.d.ts +1 -1
  223. package/dist/features/comments/schema.d.ts +2 -52
  224. package/dist/features/comments/schema.js.map +1 -1
  225. package/dist/features/comments/snapshot-equality.js.map +1 -1
  226. package/dist/features/comments/use-comments.d.ts +3 -2
  227. package/dist/features/comments/vue.d.ts +1 -1
  228. package/dist/features/composites/accepted-children.d.ts +1 -0
  229. package/dist/features/composites/accepted-children.js +16 -1
  230. package/dist/features/composites/accepted-children.js.map +1 -1
  231. package/dist/features/composites/ai-brief.js +31 -0
  232. package/dist/features/composites/ai-brief.js.map +1 -1
  233. package/dist/features/composites/bibliography-command.d.ts +11 -0
  234. package/dist/features/composites/bibliography-form.js +90 -0
  235. package/dist/features/composites/bibliography-form.js.map +1 -0
  236. package/dist/features/composites/bibliography.d.ts +33 -0
  237. package/dist/features/composites/bibliography.js +120 -0
  238. package/dist/features/composites/bibliography.js.map +1 -0
  239. package/dist/features/composites/components/BibliographyEditor.js +261 -0
  240. package/dist/features/composites/components/BibliographyEditor.js.map +1 -0
  241. package/dist/features/composites/components/CompositeChrome.js +75 -26
  242. package/dist/features/composites/components/CompositeChrome.js.map +1 -1
  243. package/dist/features/composites/components/CompositeDetails.js +46 -11
  244. package/dist/features/composites/components/CompositeDetails.js.map +1 -1
  245. package/dist/features/composites/components/CompositeDetails.vue.d.ts +2 -1
  246. package/dist/features/composites/components/CompositeFieldEditor.js +37 -4
  247. package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
  248. package/dist/features/composites/components/CompositeInspector.js +17 -5
  249. package/dist/features/composites/components/CompositeInspector.js.map +1 -1
  250. package/dist/features/composites/components/CompositeTable.js +13 -4
  251. package/dist/features/composites/components/CompositeTable.js.map +1 -1
  252. package/dist/features/composites/components/CompositeTagsEditor.js +1 -1
  253. package/dist/features/composites/components/CompositeView.js +11 -1
  254. package/dist/features/composites/components/CompositeView.js.map +1 -1
  255. package/dist/features/composites/components/DerivationBadge.js +103 -0
  256. package/dist/features/composites/components/DerivationBadge.js.map +1 -0
  257. package/dist/features/composites/components/ReferenceFieldSelect.js +60 -0
  258. package/dist/features/composites/components/ReferenceFieldSelect.js.map +1 -0
  259. package/dist/features/composites/components/ReferenceView.js +122 -0
  260. package/dist/features/composites/components/ReferenceView.js.map +1 -0
  261. package/dist/features/composites/components/ReferenceView.vue.d.ts +96 -0
  262. package/dist/features/composites/components/SettingsEditingTab.js +1 -1
  263. package/dist/features/composites/convert.js +2 -2
  264. package/dist/features/composites/convert.js.map +1 -1
  265. package/dist/features/composites/derivation.d.ts +61 -0
  266. package/dist/features/composites/derivation.js +98 -0
  267. package/dist/features/composites/derivation.js.map +1 -0
  268. package/dist/features/composites/derived-chrome.js +69 -0
  269. package/dist/features/composites/derived-chrome.js.map +1 -0
  270. package/dist/features/composites/feature.js +159 -109
  271. package/dist/features/composites/feature.js.map +1 -1
  272. package/dist/features/composites/fences.d.ts +4 -3
  273. package/dist/features/composites/fences.js +51 -7
  274. package/dist/features/composites/fences.js.map +1 -1
  275. package/dist/features/composites/index.d.ts +12 -3
  276. package/dist/features/composites/index.js +15 -6
  277. package/dist/features/composites/inspector-model.d.ts +4 -1
  278. package/dist/features/composites/inspector-model.js +8 -4
  279. package/dist/features/composites/inspector-model.js.map +1 -1
  280. package/dist/features/composites/kinds.d.ts +8 -1
  281. package/dist/features/composites/kinds.js +145 -6
  282. package/dist/features/composites/kinds.js.map +1 -1
  283. package/dist/features/composites/legacy.d.ts +4 -1
  284. package/dist/features/composites/legacy.js +56 -1
  285. package/dist/features/composites/legacy.js.map +1 -1
  286. package/dist/features/composites/meta-display.js +40 -1
  287. package/dist/features/composites/meta-display.js.map +1 -1
  288. package/dist/features/composites/meta-keys.d.ts +7 -0
  289. package/dist/features/composites/meta-keys.js +21 -0
  290. package/dist/features/composites/meta-keys.js.map +1 -0
  291. package/dist/features/composites/node-views.d.ts +6 -3
  292. package/dist/features/composites/node-views.js +7 -3
  293. package/dist/features/composites/node-views.js.map +1 -1
  294. package/dist/features/composites/nodes.d.ts +6 -1
  295. package/dist/features/composites/nodes.js +33 -7
  296. package/dist/features/composites/nodes.js.map +1 -1
  297. package/dist/features/composites/react/BibliographyEditor.js +229 -0
  298. package/dist/features/composites/react/BibliographyEditor.js.map +1 -0
  299. package/dist/features/composites/react/CompositeChrome.js +65 -17
  300. package/dist/features/composites/react/CompositeChrome.js.map +1 -1
  301. package/dist/features/composites/react/CompositeDetails.d.ts +2 -1
  302. package/dist/features/composites/react/CompositeDetails.js +45 -6
  303. package/dist/features/composites/react/CompositeDetails.js.map +1 -1
  304. package/dist/features/composites/react/CompositeFieldEditor.d.ts +1 -1
  305. package/dist/features/composites/react/CompositeFieldEditor.js +24 -1
  306. package/dist/features/composites/react/CompositeFieldEditor.js.map +1 -1
  307. package/dist/features/composites/react/CompositeInspector.js +11 -5
  308. package/dist/features/composites/react/CompositeInspector.js.map +1 -1
  309. package/dist/features/composites/react/CompositeStatusMenu.js +1 -1
  310. package/dist/features/composites/react/CompositeSummaryEditor.js +1 -1
  311. package/dist/features/composites/react/CompositeTable.js +16 -2
  312. package/dist/features/composites/react/CompositeTable.js.map +1 -1
  313. package/dist/features/composites/react/CompositeTagsEditor.js +2 -2
  314. package/dist/features/composites/react/CompositeView.js +6 -1
  315. package/dist/features/composites/react/CompositeView.js.map +1 -1
  316. package/dist/features/composites/react/DerivationBadge.js +116 -0
  317. package/dist/features/composites/react/DerivationBadge.js.map +1 -0
  318. package/dist/features/composites/react/ReferenceFieldSelect.js +52 -0
  319. package/dist/features/composites/react/ReferenceFieldSelect.js.map +1 -0
  320. package/dist/features/composites/react/ReferenceView.d.ts +11 -0
  321. package/dist/features/composites/react/ReferenceView.js +115 -0
  322. package/dist/features/composites/react/ReferenceView.js.map +1 -0
  323. package/dist/features/composites/react/SettingsEditingTab.js +2 -2
  324. package/dist/features/composites/react.d.ts +2 -1
  325. package/dist/features/composites/react.js +7 -3
  326. package/dist/features/composites/react.js.map +1 -1
  327. package/dist/features/composites/reference-ai.js +16 -0
  328. package/dist/features/composites/reference-ai.js.map +1 -0
  329. package/dist/features/composites/reference-label.d.ts +19 -0
  330. package/dist/features/composites/reference-label.js +58 -0
  331. package/dist/features/composites/reference-label.js.map +1 -0
  332. package/dist/features/composites/reference-suggestion.d.ts +10 -0
  333. package/dist/features/composites/reference-suggestion.js +41 -0
  334. package/dist/features/composites/reference-suggestion.js.map +1 -0
  335. package/dist/features/composites/reference-text.d.ts +18 -0
  336. package/dist/features/composites/reference-text.js +89 -0
  337. package/dist/features/composites/reference-text.js.map +1 -0
  338. package/dist/features/composites/reference.d.ts +44 -0
  339. package/dist/features/composites/reference.js +148 -0
  340. package/dist/features/composites/reference.js.map +1 -0
  341. package/dist/features/composites/reveal-source.d.ts +32 -0
  342. package/dist/features/composites/reveal-source.js +89 -0
  343. package/dist/features/composites/reveal-source.js.map +1 -0
  344. package/dist/features/composites/rows.d.ts +21 -2
  345. package/dist/features/composites/rows.js +63 -9
  346. package/dist/features/composites/rows.js.map +1 -1
  347. package/dist/features/composites/schema.d.ts +9 -3
  348. package/dist/features/composites/schema.js +11 -4
  349. package/dist/features/composites/schema.js.map +1 -1
  350. package/dist/features/composites/table-model.js +12 -21
  351. package/dist/features/composites/table-model.js.map +1 -1
  352. package/dist/features/composites/vue.d.ts +3 -2
  353. package/dist/features/composites/vue.js +8 -4
  354. package/dist/features/composites/vue.js.map +1 -1
  355. package/dist/features/media/assets.d.ts +2 -0
  356. package/dist/features/media/assets.js +3 -2
  357. package/dist/features/media/assets.js.map +1 -1
  358. package/dist/features/media/components/FileView.js +13 -4
  359. package/dist/features/media/components/FileView.js.map +1 -1
  360. package/dist/features/media/feature.d.ts +3 -3
  361. package/dist/features/media/feature.js +21 -28
  362. package/dist/features/media/feature.js.map +1 -1
  363. package/dist/features/media/file-import.js +2 -2
  364. package/dist/features/media/file-import.js.map +1 -1
  365. package/dist/features/media/index.d.ts +2 -2
  366. package/dist/features/media/index.js +2 -2
  367. package/dist/features/media/react/FileView.js +19 -2
  368. package/dist/features/media/react/FileView.js.map +1 -1
  369. package/dist/features/media/schema.d.ts +8 -0
  370. package/dist/features/media/schema.js +126 -11
  371. package/dist/features/media/schema.js.map +1 -1
  372. package/dist/features/media/upload.d.ts +16 -14
  373. package/dist/features/media/upload.js +13 -19
  374. package/dist/features/media/upload.js.map +1 -1
  375. package/dist/features/mentions/index.d.ts +4 -3
  376. package/dist/features/mentions/index.js +4 -1
  377. package/dist/features/mentions/index.js.map +1 -1
  378. package/dist/features/mentions/mention-text.d.ts +1 -1
  379. package/dist/features/mentions/mention-text.js.map +1 -1
  380. package/dist/features/mentions/mention.d.ts +0 -6
  381. package/dist/features/mentions/mention.js.map +1 -1
  382. package/dist/features/mentions/metadata-defaults.d.ts +2 -2
  383. package/dist/features/mentions/metadata-defaults.js +3 -9
  384. package/dist/features/mentions/metadata-defaults.js.map +1 -1
  385. package/dist/features/mentions/schema.d.ts +2 -1
  386. package/dist/features/mentions/schema.js.map +1 -1
  387. package/dist/features/tables/feature.d.ts +2 -1
  388. package/dist/features/tables/feature.js +90 -70
  389. package/dist/features/tables/feature.js.map +1 -1
  390. package/dist/features/themes/components/SettingsAppearanceTab.js +6 -9
  391. package/dist/features/themes/components/SettingsAppearanceTab.js.map +1 -1
  392. package/dist/features/themes/feature.js +2 -0
  393. package/dist/features/themes/feature.js.map +1 -1
  394. package/dist/features/themes/index.d.ts +2 -1
  395. package/dist/features/themes/react/SettingsAppearanceTab.js +7 -10
  396. package/dist/features/themes/react/SettingsAppearanceTab.js.map +1 -1
  397. package/dist/features/themes/schema.d.ts +2 -6
  398. package/dist/features/themes/schema.js.map +1 -1
  399. package/dist/features/themes/theme-runtime.d.ts +1 -1
  400. package/dist/features/themes/theme-runtime.js +4 -2
  401. package/dist/features/themes/theme-runtime.js.map +1 -1
  402. package/dist/features/transcript/TranscriptSegmentView.js +48 -28
  403. package/dist/features/transcript/TranscriptSegmentView.js.map +1 -1
  404. package/dist/features/transcript/feature.js +21 -19
  405. package/dist/features/transcript/feature.js.map +1 -1
  406. package/dist/features/transcript/react/TranscriptSegmentView.js +55 -36
  407. package/dist/features/transcript/react/TranscriptSegmentView.js.map +1 -1
  408. package/dist/features/transcript/segment-node.d.ts +2 -0
  409. package/dist/features/transcript/segment-node.js +35 -7
  410. package/dist/features/transcript/segment-node.js.map +1 -1
  411. package/dist/i18n/de/composites.js +91 -1
  412. package/dist/i18n/de/composites.js.map +1 -1
  413. package/dist/i18n/de/integrations.js +49 -8
  414. package/dist/i18n/de/integrations.js.map +1 -1
  415. package/dist/i18n/de/media.js +0 -1
  416. package/dist/i18n/de/media.js.map +1 -1
  417. package/dist/i18n/de/palette.js +22 -0
  418. package/dist/i18n/de/palette.js.map +1 -0
  419. package/dist/i18n/de/tables.js +0 -1
  420. package/dist/i18n/de/tables.js.map +1 -1
  421. package/dist/i18n/de/transcript.js +2 -1
  422. package/dist/i18n/de/transcript.js.map +1 -1
  423. package/dist/i18n/de/tree.js +3 -0
  424. package/dist/i18n/de/tree.js.map +1 -1
  425. package/dist/i18n/de.d.ts +157 -11
  426. package/dist/i18n/de.js +3 -1
  427. package/dist/i18n/de.js.map +1 -1
  428. package/dist/i18n/en/composites.js +89 -1
  429. package/dist/i18n/en/composites.js.map +1 -1
  430. package/dist/i18n/en/integrations.js +49 -8
  431. package/dist/i18n/en/integrations.js.map +1 -1
  432. package/dist/i18n/en/media.js +0 -1
  433. package/dist/i18n/en/media.js.map +1 -1
  434. package/dist/i18n/en/palette.js +22 -0
  435. package/dist/i18n/en/palette.js.map +1 -0
  436. package/dist/i18n/en/tables.js +0 -1
  437. package/dist/i18n/en/tables.js.map +1 -1
  438. package/dist/i18n/en/transcript.js +2 -1
  439. package/dist/i18n/en/transcript.js.map +1 -1
  440. package/dist/i18n/en/tree.js +3 -0
  441. package/dist/i18n/en/tree.js.map +1 -1
  442. package/dist/i18n/en.js +3 -1
  443. package/dist/i18n/en.js.map +1 -1
  444. package/dist/index.d.ts +4 -3
  445. package/dist/integrations/composite-ai/bibliography-fetch.d.ts +31 -0
  446. package/dist/integrations/composite-ai/bibliography-fetch.js +159 -0
  447. package/dist/integrations/composite-ai/bibliography-fetch.js.map +1 -0
  448. package/dist/integrations/composite-ai/composite-fill.d.ts +30 -8
  449. package/dist/integrations/composite-ai/composite-fill.js +97 -56
  450. package/dist/integrations/composite-ai/composite-fill.js.map +1 -1
  451. package/dist/integrations/composite-ai/composite-summary.d.ts +4 -12
  452. package/dist/integrations/composite-ai/composite-summary.js +7 -26
  453. package/dist/integrations/composite-ai/composite-summary.js.map +1 -1
  454. package/dist/integrations/composite-ai/derive.d.ts +29 -0
  455. package/dist/integrations/composite-ai/derive.js +173 -0
  456. package/dist/integrations/composite-ai/derive.js.map +1 -0
  457. package/dist/integrations/composite-ai/index.d.ts +11 -7
  458. package/dist/integrations/composite-ai/index.js +72 -14
  459. package/dist/integrations/composite-ai/index.js.map +1 -1
  460. package/dist/integrations/composite-ai/rendition.d.ts +45 -0
  461. package/dist/integrations/composite-ai/rendition.js +314 -0
  462. package/dist/integrations/composite-ai/rendition.js.map +1 -0
  463. package/dist/integrations/composite-media/index.d.ts +7 -5
  464. package/dist/integrations/composite-media/index.js +86 -13
  465. package/dist/integrations/composite-media/index.js.map +1 -1
  466. package/dist/integrations/transcript-ai/index.d.ts +1 -1
  467. package/dist/integrations/transcript-ai/index.js +5 -2
  468. package/dist/integrations/transcript-ai/index.js.map +1 -1
  469. package/dist/integrations/transcript-ai/protocol.d.ts +2 -12
  470. package/dist/integrations/transcript-ai/protocol.js +0 -2
  471. package/dist/integrations/transcript-ai/protocol.js.map +1 -1
  472. package/dist/integrations/transcript-ai/transcript-cleanup.d.ts +5 -5
  473. package/dist/integrations/transcript-ai/transcript-cleanup.js +7 -10
  474. package/dist/integrations/transcript-ai/transcript-cleanup.js.map +1 -1
  475. package/dist/markdown/MarkdownSurface.vue.d.ts +10 -9
  476. package/dist/markdown/MarkdownSurface2.js +3 -6
  477. package/dist/markdown/MarkdownSurface2.js.map +1 -1
  478. package/dist/markdown/document.js +1 -1
  479. package/dist/markdown/source-mode.d.ts +1 -1
  480. package/dist/nuxt/module.d.ts +1402 -54
  481. package/dist/nuxt/module.js +6 -49
  482. package/dist/nuxt/module.js.map +1 -1
  483. package/dist/nuxt/runtime/install-context.d.ts +7 -3
  484. package/dist/nuxt/runtime/install-context.js +14 -8
  485. package/dist/nuxt/runtime/install-context.js.map +1 -1
  486. package/dist/nuxt/runtime/plugins/editor-context-nuxt-ui.client.ts +2 -2
  487. package/dist/nuxt/runtime/server/plugins/editor-server.js +20 -12
  488. package/dist/nuxt/runtime/server/plugins/editor-server.js.map +1 -1
  489. package/dist/presets/compose.d.ts +94 -0
  490. package/dist/presets/compose.js +164 -0
  491. package/dist/presets/compose.js.map +1 -0
  492. package/dist/react/AdvancedEditor.d.ts +15 -15
  493. package/dist/react/AdvancedEditor.js +15 -13
  494. package/dist/react/AdvancedEditor.js.map +1 -1
  495. package/dist/react/EditorCommandPalette.d.ts +13 -0
  496. package/dist/react/EditorCommandPalette.js +118 -0
  497. package/dist/react/EditorCommandPalette.js.map +1 -0
  498. package/dist/react/EditorSettingsModal.d.ts +5 -5
  499. package/dist/react/EditorSettingsModal.js +7 -4
  500. package/dist/react/EditorSettingsModal.js.map +1 -1
  501. package/dist/react/EditorSettingsPanel.d.ts +7 -7
  502. package/dist/react/EditorSettingsPanel.js +8 -5
  503. package/dist/react/EditorSettingsPanel.js.map +1 -1
  504. package/dist/react/EditorSurface.d.ts +7 -7
  505. package/dist/react/EditorSurface.js +10 -6
  506. package/dist/react/EditorSurface.js.map +1 -1
  507. package/dist/react/EditorToolbar.d.ts +4 -6
  508. package/dist/react/EditorToolbar.js +29 -37
  509. package/dist/react/EditorToolbar.js.map +1 -1
  510. package/dist/react/SimpleEditor.d.ts +22 -23
  511. package/dist/react/SimpleEditor.js +21 -7
  512. package/dist/react/SimpleEditor.js.map +1 -1
  513. package/dist/react/advanced.d.ts +4 -3
  514. package/dist/react/advanced.js +2 -2
  515. package/dist/react/compose.d.ts +20 -0
  516. package/dist/react/compose.js +49 -0
  517. package/dist/react/compose.js.map +1 -0
  518. package/dist/react/composed-surface.d.ts +39 -0
  519. package/dist/react/composed-surface.js +71 -0
  520. package/dist/react/composed-surface.js.map +1 -0
  521. package/dist/react/context.js +6 -2
  522. package/dist/react/context.js.map +1 -1
  523. package/dist/react/index.d.ts +2 -1
  524. package/dist/react/index.js +2 -1
  525. package/dist/react/key-labels.js +15 -0
  526. package/dist/react/key-labels.js.map +1 -0
  527. package/dist/react/settings.d.ts +1 -1
  528. package/dist/react/simple.d.ts +2 -2
  529. package/dist/react/ui/command.js +36 -1
  530. package/dist/react/ui/command.js.map +1 -1
  531. package/dist/schema/advanced.d.ts +6 -6
  532. package/dist/schema/advanced.js +26 -29
  533. package/dist/schema/advanced.js.map +1 -1
  534. package/dist/server/ai.d.ts +5 -16
  535. package/dist/server/ai.js.map +1 -1
  536. package/dist/server/auth.d.ts +9 -0
  537. package/dist/server/collab-state.d.ts +1 -1
  538. package/dist/server/collab-state.js.map +1 -1
  539. package/dist/server/collab.d.ts +13 -13
  540. package/dist/server/collab.js +34 -18
  541. package/dist/server/collab.js.map +1 -1
  542. package/dist/server/crossref.js +78 -0
  543. package/dist/server/crossref.js.map +1 -0
  544. package/dist/server/errors.d.ts +1 -1
  545. package/dist/server/errors.js +1 -1
  546. package/dist/server/errors.js.map +1 -1
  547. package/dist/server/extract.js +308 -0
  548. package/dist/server/extract.js.map +1 -0
  549. package/dist/{nuxt/runtime/server/utils/editor-route.js → server/h3.js} +8 -16
  550. package/dist/server/h3.js.map +1 -0
  551. package/dist/server/handlers/ai.d.ts +30 -9
  552. package/dist/server/handlers/ai.js +170 -36
  553. package/dist/server/handlers/ai.js.map +1 -1
  554. package/dist/server/handlers/context.d.ts +12 -5
  555. package/dist/server/handlers/context.js +10 -4
  556. package/dist/server/handlers/context.js.map +1 -1
  557. package/dist/server/handlers/editor.d.ts +19 -13
  558. package/dist/server/handlers/editor.js +49 -38
  559. package/dist/server/handlers/editor.js.map +1 -1
  560. package/dist/server/handlers/index.d.ts +4 -4
  561. package/dist/server/handlers/index.js +4 -4
  562. package/dist/server/host.d.ts +16 -25
  563. package/dist/server/host.js +58 -41
  564. package/dist/server/host.js.map +1 -1
  565. package/dist/server/index.d.ts +6 -4
  566. package/dist/server/index.js +3 -2
  567. package/dist/server/pdf.js +97 -0
  568. package/dist/server/pdf.js.map +1 -0
  569. package/dist/server/{adapter.d.ts → ports.d.ts} +81 -30
  570. package/dist/server/ports.js +29 -0
  571. package/dist/server/ports.js.map +1 -0
  572. package/dist/server/routes.js +144 -0
  573. package/dist/server/routes.js.map +1 -0
  574. package/dist/settings/index.d.ts +1 -1
  575. package/dist/settings/index.js.map +1 -1
  576. package/dist/styles/composites.css +25 -0
  577. package/dist/tree/DocumentTree.js +56 -29
  578. package/dist/tree/DocumentTree.js.map +1 -1
  579. package/dist/tree/react/DocumentTree.js +23 -0
  580. package/dist/tree/react/DocumentTree.js.map +1 -1
  581. package/dist/tree/tree-host.js +1 -1
  582. package/dist/vue/AdvancedEditor.js +30 -9
  583. package/dist/vue/AdvancedEditor.js.map +1 -1
  584. package/dist/vue/AdvancedEditor.vue.d.ts +32 -12
  585. package/dist/vue/SimpleEditor.js +62 -16
  586. package/dist/vue/SimpleEditor.js.map +1 -1
  587. package/dist/vue/SimpleEditor.vue.d.ts +37 -7
  588. package/dist/vue/advanced.d.ts +3 -2
  589. package/dist/vue/advanced.js +2 -2
  590. package/dist/vue/components/EditorCommandPalette.js +91 -0
  591. package/dist/vue/components/EditorCommandPalette.js.map +1 -0
  592. package/dist/vue/components/EditorCommandPalette.vue.d.ts +15 -0
  593. package/dist/vue/components/EditorSettingsModal.js +10 -6
  594. package/dist/vue/components/EditorSettingsModal.js.map +1 -1
  595. package/dist/vue/components/EditorSettingsModal.vue.d.ts +4 -4
  596. package/dist/vue/components/EditorSettingsPanel.js +10 -6
  597. package/dist/vue/components/EditorSettingsPanel.js.map +1 -1
  598. package/dist/vue/components/EditorSettingsPanel.vue.d.ts +6 -6
  599. package/dist/vue/components/EditorSurface.js +11 -6
  600. package/dist/vue/components/EditorSurface.js.map +1 -1
  601. package/dist/vue/components/EditorSurface.vue.d.ts +11 -11
  602. package/dist/vue/components/EditorToolbar.js +41 -49
  603. package/dist/vue/components/EditorToolbar.js.map +1 -1
  604. package/dist/vue/components/EditorToolbar.vue.d.ts +2 -4
  605. package/dist/vue/compose.d.ts +17 -0
  606. package/dist/vue/compose.js +51 -0
  607. package/dist/vue/compose.js.map +1 -0
  608. package/dist/vue/context-key.d.ts +6 -0
  609. package/dist/vue/context-key.js +6 -0
  610. package/dist/vue/context-key.js.map +1 -0
  611. package/dist/vue/context.d.ts +8 -6
  612. package/dist/vue/context.js +6 -5
  613. package/dist/vue/context.js.map +1 -1
  614. package/dist/vue/index.d.ts +10 -8
  615. package/dist/vue/index.js +10 -8
  616. package/dist/vue/kit/index.d.ts +2 -2
  617. package/dist/vue/kit/nuxt-ui.js +23 -0
  618. package/dist/vue/kit/nuxt-ui.js.map +1 -1
  619. package/dist/vue/kit/types.d.ts +40 -1
  620. package/package.json +30 -16
  621. package/dist/adapters/comments/client.d.ts +0 -64
  622. package/dist/adapters/comments/client.js.map +0 -1
  623. package/dist/adapters/routes.js.map +0 -1
  624. package/dist/core/toolbar.d.ts +0 -74
  625. package/dist/core/toolbar.js +0 -45
  626. package/dist/core/toolbar.js.map +0 -1
  627. package/dist/integrations/composite-ai/document-transcribe.d.ts +0 -14
  628. package/dist/integrations/composite-ai/document-transcribe.js +0 -89
  629. package/dist/integrations/composite-ai/document-transcribe.js.map +0 -1
  630. package/dist/nuxt/runtime/server/api/ai/chat.post.d.ts +0 -5
  631. package/dist/nuxt/runtime/server/api/ai/chat.post.js +0 -8
  632. package/dist/nuxt/runtime/server/api/ai/chat.post.js.map +0 -1
  633. package/dist/nuxt/runtime/server/api/ai/complete.post.d.ts +0 -5
  634. package/dist/nuxt/runtime/server/api/ai/complete.post.js +0 -8
  635. package/dist/nuxt/runtime/server/api/ai/complete.post.js.map +0 -1
  636. package/dist/nuxt/runtime/server/api/ai/generate.post.d.ts +0 -5
  637. package/dist/nuxt/runtime/server/api/ai/generate.post.js +0 -8
  638. package/dist/nuxt/runtime/server/api/ai/generate.post.js.map +0 -1
  639. package/dist/nuxt/runtime/server/api/ai/models.get.d.ts +0 -5
  640. package/dist/nuxt/runtime/server/api/ai/models.get.js +0 -8
  641. package/dist/nuxt/runtime/server/api/ai/models.get.js.map +0 -1
  642. package/dist/nuxt/runtime/server/api/ai/transcribe.post.d.ts +0 -5
  643. package/dist/nuxt/runtime/server/api/ai/transcribe.post.js +0 -8
  644. package/dist/nuxt/runtime/server/api/ai/transcribe.post.js.map +0 -1
  645. package/dist/nuxt/runtime/server/api/ai/transcript-cleanup.post.d.ts +0 -5
  646. package/dist/nuxt/runtime/server/api/ai/transcript-cleanup.post.js +0 -8
  647. package/dist/nuxt/runtime/server/api/ai/transcript-cleanup.post.js.map +0 -1
  648. package/dist/nuxt/runtime/server/api/editor/assets/[id]/[name].get.d.ts +0 -5
  649. package/dist/nuxt/runtime/server/api/editor/assets/[id]/[name].get.js +0 -8
  650. package/dist/nuxt/runtime/server/api/editor/assets/[id]/[name].get.js.map +0 -1
  651. package/dist/nuxt/runtime/server/api/editor/assets/[id].post.d.ts +0 -5
  652. package/dist/nuxt/runtime/server/api/editor/assets/[id].post.js +0 -8
  653. package/dist/nuxt/runtime/server/api/editor/assets/[id].post.js.map +0 -1
  654. package/dist/nuxt/runtime/server/api/editor/settings.get.d.ts +0 -5
  655. package/dist/nuxt/runtime/server/api/editor/settings.get.js +0 -8
  656. package/dist/nuxt/runtime/server/api/editor/settings.get.js.map +0 -1
  657. package/dist/nuxt/runtime/server/api/editor/settings.put.d.ts +0 -5
  658. package/dist/nuxt/runtime/server/api/editor/settings.put.js +0 -8
  659. package/dist/nuxt/runtime/server/api/editor/settings.put.js.map +0 -1
  660. package/dist/nuxt/runtime/server/api/editor/tags.get.d.ts +0 -5
  661. package/dist/nuxt/runtime/server/api/editor/tags.get.js +0 -8
  662. package/dist/nuxt/runtime/server/api/editor/tags.get.js.map +0 -1
  663. package/dist/nuxt/runtime/server/api/editor/themes/[id].delete.d.ts +0 -5
  664. package/dist/nuxt/runtime/server/api/editor/themes/[id].delete.js +0 -8
  665. package/dist/nuxt/runtime/server/api/editor/themes/[id].delete.js.map +0 -1
  666. package/dist/nuxt/runtime/server/api/editor/themes/[id].get.d.ts +0 -5
  667. package/dist/nuxt/runtime/server/api/editor/themes/[id].get.js +0 -8
  668. package/dist/nuxt/runtime/server/api/editor/themes/[id].get.js.map +0 -1
  669. package/dist/nuxt/runtime/server/api/editor/themes/index.get.d.ts +0 -5
  670. package/dist/nuxt/runtime/server/api/editor/themes/index.get.js +0 -8
  671. package/dist/nuxt/runtime/server/api/editor/themes/index.get.js.map +0 -1
  672. package/dist/nuxt/runtime/server/api/editor/themes/index.post.d.ts +0 -5
  673. package/dist/nuxt/runtime/server/api/editor/themes/index.post.js +0 -8
  674. package/dist/nuxt/runtime/server/api/editor/themes/index.post.js.map +0 -1
  675. package/dist/nuxt/runtime/server/api/editor/users.get.d.ts +0 -5
  676. package/dist/nuxt/runtime/server/api/editor/users.get.js +0 -8
  677. package/dist/nuxt/runtime/server/api/editor/users.get.js.map +0 -1
  678. package/dist/nuxt/runtime/server/plugins/collab.d.ts +0 -5
  679. package/dist/nuxt/runtime/server/plugins/collab.js +0 -10
  680. package/dist/nuxt/runtime/server/plugins/collab.js.map +0 -1
  681. package/dist/nuxt/runtime/server/routes/collab/[id].d.ts +0 -5
  682. package/dist/nuxt/runtime/server/routes/collab/[id].js +0 -35
  683. package/dist/nuxt/runtime/server/routes/collab/[id].js.map +0 -1
  684. package/dist/nuxt/runtime/server/utils/editor-route.d.ts +0 -11
  685. package/dist/nuxt/runtime/server/utils/editor-route.js.map +0 -1
  686. package/dist/presets/advanced.d.ts +0 -33
  687. package/dist/presets/advanced.js +0 -50
  688. package/dist/presets/advanced.js.map +0 -1
  689. package/dist/presets/simple.d.ts +0 -9
  690. package/dist/presets/simple.js +0 -12
  691. package/dist/presets/simple.js.map +0 -1
  692. package/dist/react/advanced-preset.d.ts +0 -11
  693. package/dist/react/advanced-preset.js +0 -31
  694. package/dist/react/advanced-preset.js.map +0 -1
  695. package/dist/vue/advanced-preset.d.ts +0 -13
  696. package/dist/vue/advanced-preset.js +0 -33
  697. package/dist/vue/advanced-preset.js.map +0 -1
  698. package/docs/extending.md +0 -538
  699. package/docs/integration.md +0 -630
  700. package/docs/llms.md +0 -383
  701. package/docs/usage.md +0 -1076
@@ -1,8 +1,9 @@
1
1
  import { metadataOf } from "../../core/metadata.js";
2
2
  import { METADATA_LIST } from "../../core/metadata-types.js";
3
+ import { formatSegmentTime } from "./segment-node.js";
3
4
  import { useEditorI18n } from "../../vue/use-editor-i18n.js";
4
5
  import _sfc_main$1 from "../../vue/components/SuggestionList.js";
5
- import { computed, createBlock, createCommentVNode, createElementVNode, createVNode, defineComponent, normalizeClass, onBeforeUnmount, openBlock, ref, unref, useTemplateRef, watch, withCtx } from "vue";
6
+ import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, onBeforeUnmount, openBlock, ref, toDisplayString, unref, useTemplateRef, watch, withCtx, withModifiers } from "vue";
6
7
  import { NodeViewContent, NodeViewWrapper, nodeViewProps } from "@tiptap/vue-3";
7
8
  //#region src/features/transcript/TranscriptSegmentView.vue
8
9
  const _hoisted_1 = { class: "grid grid-cols-[5.5rem_1fr] items-start gap-2" };
@@ -18,6 +19,7 @@ const _hoisted_3 = [
18
19
  "aria-expanded",
19
20
  "readonly"
20
21
  ];
22
+ const _hoisted_4 = ["title", "aria-label"];
21
23
  const _sfc_main = /*@__PURE__*/ defineComponent({
22
24
  __name: "TranscriptSegmentView",
23
25
  props: nodeViewProps,
@@ -125,39 +127,57 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
125
127
  props.editor.commands.focus(props.getPos() + props.node.nodeSize - 1);
126
128
  }
127
129
  }
130
+ /** Seeks and plays the source recording of the enclosing Baustein (a no-op without the composites feature or a source). */
131
+ function seek() {
132
+ const start = props.node.attrs.start;
133
+ const pos = props.getPos();
134
+ if (typeof start === "number" && pos !== void 0) props.editor.commands.seekSource?.(pos, start);
135
+ }
128
136
  return (_ctx, _cache) => {
129
137
  return openBlock(), createBlock(unref(NodeViewWrapper), {
130
138
  class: normalizeClass(["transcript-segment my-2", { "is-selected rounded-md ring-1 ring-[var(--sk-primary)]": _ctx.selected }]),
131
139
  "data-type": "transcript-segment",
132
140
  "data-id": _ctx.node.attrs.id || void 0
133
141
  }, {
134
- default: withCtx(() => [createElementVNode("div", _hoisted_1, [createElementVNode("div", _hoisted_2, [createElementVNode("input", {
135
- value: draft.value,
136
- title: draft.value || void 0,
137
- placeholder: unref(t)("transcript.speaker"),
138
- "aria-label": unref(t)("transcript.speaker"),
139
- autocomplete: "off",
140
- role: "combobox",
141
- "aria-expanded": open.value && items.value.length > 0,
142
- readonly: readonly.value,
143
- 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)]",
144
- onFocus,
145
- onBlur,
146
- onMousedown: _cache[0] || (_cache[0] = ($event) => openList("")),
147
- onInput,
148
- onKeydown: onKeyDown
149
- }, null, 40, _hoisted_3), open.value && items.value.length ? (openBlock(), createBlock(_sfc_main$1, {
150
- key: 0,
151
- ref_key: "list",
152
- ref: list,
153
- class: "absolute top-full left-0 z-50 mt-1",
154
- items: items.value,
155
- display: (item) => ({ label: item.label }),
156
- command: pick,
157
- loading: false,
158
- "loading-text": "",
159
- "empty-text": ""
160
- }, null, 8, ["items", "display"])) : createCommentVNode("v-if", true)]), createVNode(unref(NodeViewContent), { class: "transcript-segment__text" })])]),
142
+ default: withCtx(() => [createElementVNode("div", _hoisted_1, [createElementVNode("div", _hoisted_2, [
143
+ createElementVNode("input", {
144
+ value: draft.value,
145
+ title: draft.value || void 0,
146
+ placeholder: unref(t)("transcript.speaker"),
147
+ "aria-label": unref(t)("transcript.speaker"),
148
+ autocomplete: "off",
149
+ role: "combobox",
150
+ "aria-expanded": open.value && items.value.length > 0,
151
+ readonly: readonly.value,
152
+ 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)]",
153
+ onFocus,
154
+ onBlur,
155
+ onMousedown: _cache[0] || (_cache[0] = ($event) => openList("")),
156
+ onInput,
157
+ onKeydown: onKeyDown
158
+ }, null, 40, _hoisted_3),
159
+ _ctx.node.attrs.start != null ? (openBlock(), createElementBlock("button", {
160
+ key: 0,
161
+ type: "button",
162
+ class: "transcript-segment__time block rounded px-1 text-xs tabular-nums text-[var(--sk-muted)] hover:text-[var(--sk-primary)] hover:underline",
163
+ title: unref(t)("transcript.seek", { time: unref(formatSegmentTime)(_ctx.node.attrs.start) }),
164
+ "aria-label": unref(t)("transcript.seek", { time: unref(formatSegmentTime)(_ctx.node.attrs.start) }),
165
+ onMousedown: _cache[1] || (_cache[1] = withModifiers(() => {}, ["prevent"])),
166
+ onClick: seek
167
+ }, toDisplayString(unref(formatSegmentTime)(_ctx.node.attrs.start)), 41, _hoisted_4)) : createCommentVNode("v-if", true),
168
+ open.value && items.value.length ? (openBlock(), createBlock(_sfc_main$1, {
169
+ key: 1,
170
+ ref_key: "list",
171
+ ref: list,
172
+ class: "absolute top-full left-0 z-50 mt-1",
173
+ items: items.value,
174
+ display: (item) => ({ label: item.label }),
175
+ command: pick,
176
+ loading: false,
177
+ "loading-text": "",
178
+ "empty-text": ""
179
+ }, null, 8, ["items", "display"])) : createCommentVNode("v-if", true)
180
+ ]), createVNode(unref(NodeViewContent), { class: "transcript-segment__text" })])]),
161
181
  _: 1
162
182
  }, 8, ["class", "data-id"]);
163
183
  };
@@ -1 +1 @@
1
- {"version":3,"file":"TranscriptSegmentView.js","names":["selected","node","_openBlock","_createBlock","_unref","_normalizeClass","_withCtx","_createElementVNode","SuggestionList","_createCommentVNode","_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'\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</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 <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 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,8 +1,9 @@
1
1
  import { ABILITY } from "../../core/schema/abilities.js";
2
2
  import { filterMetadataItems } from "../../core/metadata.js";
3
- import { commandItem } from "../../core/toolbar.js";
3
+ import { command } from "../../core/commands.js";
4
4
  import { speakersIn } from "./speakers.js";
5
5
  import { TranscriptSegmentNode } from "./segment-node.js";
6
+ import { transcriptSchemaExtensions } from "./schema.js";
6
7
  import { speakerConversions } from "./convert.js";
7
8
  import { segmentSpeakerLines } from "./segment.js";
8
9
  //#region src/features/transcript/feature.ts
@@ -26,25 +27,26 @@ function transcriptFeatureCore(ui) {
26
27
  const TranscriptSegment = transcriptSegment(ui.nodeViews);
27
28
  return {
28
29
  id: "transcript",
30
+ dimension: "content",
31
+ schemaExtensions: transcriptSchemaExtensions,
29
32
  extensions: () => [TranscriptSegment],
30
- toolbar: (ctx) => [{
31
- id: "inserts",
32
- order: 50,
33
- items: [commandItem({
34
- id: "speaker",
35
- order: 10,
36
- icon: "i-lucide-speech",
37
- label: ctx.t("transcript.toolbar.speaker"),
38
- kbds: [
39
- "meta",
40
- "shift",
41
- "s"
42
- ],
43
- ability: ABILITY.transcript,
44
- command: (c) => c.toggleTranscriptSegment(),
45
- isActive: (e) => e.isActive("transcriptSegment")
46
- })]
47
- }],
33
+ commands: (ctx) => [command({
34
+ id: "speaker",
35
+ toolbar: {
36
+ group: "inserts",
37
+ order: 10
38
+ },
39
+ icon: "i-lucide-speech",
40
+ label: ctx.t("transcript.toolbar.speaker"),
41
+ kbds: [
42
+ "meta",
43
+ "shift",
44
+ "s"
45
+ ],
46
+ ability: ABILITY.transcript,
47
+ command: (c) => c.toggleTranscriptSegment(),
48
+ active: (e) => e.isActive("transcriptSegment")
49
+ })],
48
50
  metadataDefaults: () => ({ speakers: ({ query, editor }) => filterMetadataItems(speakersIn(editor.state.doc).map((s) => ({
49
51
  id: s,
50
52
  label: s
@@ -1 +1 @@
1
- {"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/transcript/feature.ts"],"sourcesContent":["import type { NodeViewRenderer } from '@tiptap/core'\nimport type { EditorFeature } from '../../core'\nimport { commandItem, filterMetadataItems } from '../../core'\nimport { ABILITY } from '../../core/schema'\nimport { speakerConversions } from './convert'\nimport { segmentSpeakerLines } from './segment'\nimport { TranscriptSegmentNode } from './segment-node'\nimport { speakersIn } from './speakers'\n\n/** What a framework binding supplies to the transcript feature: the node view of the speaker segment. */\nexport interface TranscriptUi {\n nodeViews: {\n transcriptSegment: NodeViewRenderer\n }\n}\n\n/**\n * The speaker segment node with the node view of a framework binding (`VueNodeViewRenderer(TranscriptSegmentView)`,\n * `ReactNodeViewRenderer(TranscriptSegmentView)`). TipTap's default `NodeView.stopEvent` already hands INPUT events to\n * the speaker field, not ProseMirror.\n */\nexport function transcriptSegment(nodeViews: TranscriptUi['nodeViews']) {\n return TranscriptSegmentNode.extend({\n // Explicit return type: keeps the declaration independent of the framework view's inferred types.\n addNodeView(): NodeViewRenderer {\n return nodeViews.transcriptSegment\n },\n })\n}\n\n/**\n * Speaker segments: the node with its speaker field, the „Sprecherblock“ toolbar button (Mod-Shift-S), the conversion\n * into speaker lines and a built-in `speakers` list (speakers already in the document, no backend). Contributes the\n * `segment` stage: `**Name:** text` lines of imported or AI-written Markdown become segments. The only framework part is\n * the node view (`ui.nodeViews`), so the descriptor carries no component slot.\n */\nexport function transcriptFeatureCore(ui: TranscriptUi): EditorFeature<never> {\n const TranscriptSegment = transcriptSegment(ui.nodeViews)\n return {\n id: 'transcript',\n extensions: () => [TranscriptSegment],\n toolbar: ctx => [{\n id: 'inserts',\n order: 50,\n items: [\n commandItem({\n id: 'speaker',\n order: 10,\n icon: 'i-lucide-speech',\n label: ctx.t('transcript.toolbar.speaker'),\n kbds: ['meta', 'shift', 's'],\n ability: ABILITY.transcript,\n command: c => c.toggleTranscriptSegment(),\n isActive: e => e.isActive('transcriptSegment'),\n }),\n ],\n }],\n metadataDefaults: () => ({\n speakers: ({ query, editor }) => filterMetadataItems(speakersIn(editor.state.doc).map(s => ({ id: s, label: s })), query),\n }),\n conversions: [speakerConversions],\n blocks: { segment: segmentSpeakerLines },\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAqBA,SAAgB,kBAAkB,WAAsC;CACtE,OAAO,sBAAsB,OAAO,EAElC,cAAgC;EAC9B,OAAO,UAAU;CACnB,EACF,CAAC;AACH;;;;;;;AAQA,SAAgB,sBAAsB,IAAwC;CAC5E,MAAM,oBAAoB,kBAAkB,GAAG,SAAS;CACxD,OAAO;EACL,IAAI;EACJ,kBAAkB,CAAC,iBAAiB;EACpC,UAAS,QAAO,CAAC;GACf,IAAI;GACJ,OAAO;GACP,OAAO,CACL,YAAY;IACV,IAAI;IACJ,OAAO;IACP,MAAM;IACN,OAAO,IAAI,EAAE,4BAA4B;IACzC,MAAM;KAAC;KAAQ;KAAS;IAAG;IAC3B,SAAS,QAAQ;IACjB,UAAS,MAAK,EAAE,wBAAwB;IACxC,WAAU,MAAK,EAAE,SAAS,mBAAmB;GAC/C,CAAC,CACH;EACF,CAAC;EACD,yBAAyB,EACvB,WAAW,EAAE,OAAO,aAAa,oBAAoB,WAAW,OAAO,MAAM,GAAG,CAAC,CAAC,KAAI,OAAM;GAAE,IAAI;GAAG,OAAO;EAAE,EAAE,GAAG,KAAK,EAC1H;EACA,aAAa,CAAC,kBAAkB;EAChC,QAAQ,EAAE,SAAS,oBAAoB;CACzC;AACF"}
1
+ {"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/transcript/feature.ts"],"sourcesContent":["import type { NodeViewRenderer } from '@tiptap/core'\nimport type { EditorFeature } from '../../core'\nimport { command, filterMetadataItems } from '../../core'\nimport { ABILITY } from '../../core/schema'\nimport { speakerConversions } from './convert'\nimport { segmentSpeakerLines } from './segment'\nimport { transcriptSchemaExtensions } from './schema'\nimport { TranscriptSegmentNode } from './segment-node'\nimport { speakersIn } from './speakers'\n\n/** What a framework binding supplies to the transcript feature: the node view of the speaker segment. */\nexport interface TranscriptUi {\n nodeViews: {\n transcriptSegment: NodeViewRenderer\n }\n}\n\n/**\n * The speaker segment node with the node view of a framework binding (`VueNodeViewRenderer(TranscriptSegmentView)`,\n * `ReactNodeViewRenderer(TranscriptSegmentView)`). TipTap's default `NodeView.stopEvent` already hands INPUT events to\n * the speaker field, not ProseMirror.\n */\nexport function transcriptSegment(nodeViews: TranscriptUi['nodeViews']) {\n return TranscriptSegmentNode.extend({\n // Explicit return type: keeps the declaration independent of the framework view's inferred types.\n addNodeView(): NodeViewRenderer {\n return nodeViews.transcriptSegment\n },\n })\n}\n\n/**\n * Speaker segments: the node with its speaker field, the „Sprecherblock“ toolbar button (Mod-Shift-S), the conversion\n * into speaker lines and a built-in `speakers` list (speakers already in the document, no backend). Contributes the\n * `segment` stage: `**Name:** text` lines of imported or AI-written Markdown become segments. The only framework part is\n * the node view (`ui.nodeViews`), so the descriptor carries no component slot.\n */\nexport function transcriptFeatureCore(ui: TranscriptUi): EditorFeature<never> {\n const TranscriptSegment = transcriptSegment(ui.nodeViews)\n return {\n id: 'transcript',\n dimension: 'content',\n schemaExtensions: transcriptSchemaExtensions,\n extensions: () => [TranscriptSegment],\n commands: ctx => [\n command({\n id: 'speaker',\n toolbar: { group: 'inserts', order: 10 },\n icon: 'i-lucide-speech',\n label: ctx.t('transcript.toolbar.speaker'),\n kbds: ['meta', 'shift', 's'],\n ability: ABILITY.transcript,\n command: c => c.toggleTranscriptSegment(),\n active: e => e.isActive('transcriptSegment'),\n }),\n ],\n metadataDefaults: () => ({\n speakers: ({ query, editor }) => filterMetadataItems(speakersIn(editor.state.doc).map(s => ({ id: s, label: s })), query),\n }),\n conversions: [speakerConversions],\n blocks: { segment: segmentSpeakerLines },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAsBA,SAAgB,kBAAkB,WAAsC;CACtE,OAAO,sBAAsB,OAAO,EAElC,cAAgC;EAC9B,OAAO,UAAU;CACnB,EACF,CAAC;AACH;;;;;;;AAQA,SAAgB,sBAAsB,IAAwC;CAC5E,MAAM,oBAAoB,kBAAkB,GAAG,SAAS;CACxD,OAAO;EACL,IAAI;EACJ,WAAW;EACX,kBAAkB;EAClB,kBAAkB,CAAC,iBAAiB;EACpC,WAAU,QAAO,CACf,QAAQ;GACN,IAAI;GACJ,SAAS;IAAE,OAAO;IAAW,OAAO;GAAG;GACvC,MAAM;GACN,OAAO,IAAI,EAAE,4BAA4B;GACzC,MAAM;IAAC;IAAQ;IAAS;GAAG;GAC3B,SAAS,QAAQ;GACjB,UAAS,MAAK,EAAE,wBAAwB;GACxC,SAAQ,MAAK,EAAE,SAAS,mBAAmB;EAC7C,CAAC,CACH;EACA,yBAAyB,EACvB,WAAW,EAAE,OAAO,aAAa,oBAAoB,WAAW,OAAO,MAAM,GAAG,CAAC,CAAC,KAAI,OAAM;GAAE,IAAI;GAAG,OAAO;EAAE,EAAE,GAAG,KAAK,EAC1H;EACA,aAAa,CAAC,kBAAkB;EAChC,QAAQ,EAAE,SAAS,oBAAoB;CACzC;AACF"}
@@ -1,5 +1,6 @@
1
1
  import { metadataOf } from "../../../core/metadata.js";
2
2
  import { METADATA_LIST } from "../../../core/metadata-types.js";
3
+ import { formatSegmentTime } from "../segment-node.js";
3
4
  import { useEditorI18n } from "../../../react/use-editor-i18n.js";
4
5
  import { cn } from "../../../react/ui/utils.js";
5
6
  import { SuggestionList } from "../../../react/suggestion-renderer.js";
@@ -99,6 +100,12 @@ function TranscriptSegmentView({ node, editor, selected, updateAttributes, getPo
99
100
  editor.commands.focus(getPos() + node.nodeSize - 1);
100
101
  }
101
102
  };
103
+ /** Seeks and plays the source recording of the enclosing Baustein (a no-op without the composites feature or a source). */
104
+ const seek = () => {
105
+ const start = node.attrs.start;
106
+ const pos = getPos();
107
+ if (typeof start === "number" && pos !== void 0) editor.commands.seekSource?.(pos, start);
108
+ };
102
109
  return /* @__PURE__ */ jsx(NodeViewWrapper, {
103
110
  className: cn("transcript-segment my-2", selected && "is-selected rounded-md ring-1 ring-primary"),
104
111
  "data-type": "transcript-segment",
@@ -108,43 +115,55 @@ function TranscriptSegmentView({ node, editor, selected, updateAttributes, getPo
108
115
  children: [/* @__PURE__ */ jsxs("div", {
109
116
  className: "transcript-segment__speaker relative pt-0.5",
110
117
  contentEditable: false,
111
- children: [/* @__PURE__ */ jsx("input", {
112
- value: draft,
113
- title: draft || void 0,
114
- placeholder: t("transcript.speaker"),
115
- "aria-label": t("transcript.speaker"),
116
- autoComplete: "off",
117
- role: "combobox",
118
- "aria-expanded": open && items.length > 0,
119
- readOnly: readonly,
120
- className: "w-full truncate rounded bg-transparent px-1 text-sm text-muted-foreground outline-none placeholder:text-muted-foreground/60 focus:bg-muted focus:text-foreground",
121
- onFocus: () => {
122
- focused.current = true;
123
- setReadonly(!editor.isEditable);
124
- },
125
- onBlur: () => {
126
- focused.current = false;
127
- closeList();
128
- commit();
129
- },
130
- onMouseDown: () => openList(""),
131
- onChange: (e) => {
132
- setDraft(e.target.value);
133
- openList(e.target.value);
134
- },
135
- onKeyDown
136
- }), open && items.length > 0 && /* @__PURE__ */ jsx("div", {
137
- className: "absolute top-full left-0 z-50 mt-1",
138
- children: /* @__PURE__ */ jsx(SuggestionList, {
139
- ref: list,
140
- items,
141
- display: displayItem,
142
- command: pick,
143
- loading: false,
144
- loadingText: "",
145
- emptyText: ""
118
+ children: [
119
+ /* @__PURE__ */ jsx("input", {
120
+ value: draft,
121
+ title: draft || void 0,
122
+ placeholder: t("transcript.speaker"),
123
+ "aria-label": t("transcript.speaker"),
124
+ autoComplete: "off",
125
+ role: "combobox",
126
+ "aria-expanded": open && items.length > 0,
127
+ readOnly: readonly,
128
+ className: "w-full truncate rounded bg-transparent px-1 text-sm text-muted-foreground outline-none placeholder:text-muted-foreground/60 focus:bg-muted focus:text-foreground",
129
+ onFocus: () => {
130
+ focused.current = true;
131
+ setReadonly(!editor.isEditable);
132
+ },
133
+ onBlur: () => {
134
+ focused.current = false;
135
+ closeList();
136
+ commit();
137
+ },
138
+ onMouseDown: () => openList(""),
139
+ onChange: (e) => {
140
+ setDraft(e.target.value);
141
+ openList(e.target.value);
142
+ },
143
+ onKeyDown
144
+ }),
145
+ node.attrs.start != null && /* @__PURE__ */ jsx("button", {
146
+ type: "button",
147
+ className: "transcript-segment__time block rounded px-1 text-xs tabular-nums text-muted-foreground hover:text-primary hover:underline",
148
+ title: t("transcript.seek", { time: formatSegmentTime(node.attrs.start) }),
149
+ "aria-label": t("transcript.seek", { time: formatSegmentTime(node.attrs.start) }),
150
+ onMouseDown: (e) => e.preventDefault(),
151
+ onClick: seek,
152
+ children: formatSegmentTime(node.attrs.start)
153
+ }),
154
+ open && items.length > 0 && /* @__PURE__ */ jsx("div", {
155
+ className: "absolute top-full left-0 z-50 mt-1",
156
+ children: /* @__PURE__ */ jsx(SuggestionList, {
157
+ ref: list,
158
+ items,
159
+ display: displayItem,
160
+ command: pick,
161
+ loading: false,
162
+ loadingText: "",
163
+ emptyText: ""
164
+ })
146
165
  })
147
- })]
166
+ ]
148
167
  }), /* @__PURE__ */ jsx(NodeViewContent, { className: "transcript-segment__text" })]
149
168
  })
150
169
  });
@@ -1 +1 @@
1
- {"version":3,"file":"TranscriptSegmentView.js","names":[],"sources":["../../../../src/features/transcript/react/TranscriptSegmentView.tsx"],"sourcesContent":["import { NodeViewContent, NodeViewWrapper, type NodeViewProps } from '@tiptap/react'\nimport { type KeyboardEvent as ReactKeyboardEvent, useCallback, useEffect, useRef, useState } from 'react'\nimport { METADATA_LIST, type MetadataItem, metadataOf } from '../../../core'\nimport { SuggestionList, type SuggestionListHandle } from '../../../react/suggestion-renderer'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\n\n// Module-level so the list's memoised rows keep their identity across renders.\nconst displayItem = (item: unknown) => ({ label: (item as MetadataItem).label })\n\n/** The speaker segment: the speaker field (with a suggestion list of the `speakers` metadata) beside the editable text. */\nexport function TranscriptSegmentView({ node, editor, selected, updateAttributes, getPos }: NodeViewProps) {\n const { t } = useEditorI18n()\n const speaker = String(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.\n const [draft, setDraft] = useState(speaker)\n const focused = useRef(false)\n useEffect(() => {\n if (!focused.current) setDraft(speaker)\n }, [speaker])\n // `editor.isEditable` is not reactive, so a read-only editor is detected when the input gets focus.\n const [readonly, setReadonly] = useState(false)\n\n const [open, setOpen] = useState(false)\n const [items, setItems] = useState<MetadataItem[]>([])\n const list = useRef<SuggestionListHandle>(null)\n\n // Same loading as the composite tags: 150 ms after typing pauses, a newer search aborts the older one (sources may be remote).\n const timer = useRef<ReturnType<typeof setTimeout>>(undefined)\n const pending = useRef<AbortController>(undefined)\n const scheduleSearch = (query: string) => {\n clearTimeout(timer.current)\n timer.current = setTimeout(async () => {\n pending.current?.abort()\n const controller = pending.current = new AbortController()\n try {\n const found = await metadataOf(editor).search(METADATA_LIST.speakers, { query, editor, signal: controller.signal })\n if (!controller.signal.aborted) setItems(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 }\n useEffect(() => () => {\n clearTimeout(timer.current)\n pending.current?.abort()\n }, [])\n\n const openList = (query: string) => {\n if (!editor.isEditable) return\n setOpen(true)\n scheduleSearch(query)\n }\n\n const closeList = useCallback(() => {\n setOpen(false)\n clearTimeout(timer.current)\n pending.current?.abort()\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`). */\n const commit = async (name = draft.trim()) => {\n setDraft(name)\n if (readonly || name === speaker) 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(editor).search(METADATA_LIST.speakers, { query: name, editor }) : null\n updateAttributes({ speaker: name })\n if (!found) return\n if (!(await found).some(item => item.label.toLowerCase() === name.toLowerCase())) {\n metadataOf(editor).create(METADATA_LIST.speakers, { id: name, label: name })\n }\n }\n\n const pick = (item: unknown) => {\n closeList()\n void commit((item as MetadataItem).label)\n }\n\n /** The speaker field of the segment `step` places before/after this one (document order), if any. */\n const neighbourField = (input: HTMLInputElement, step: -1 | 1): HTMLInputElement | undefined => {\n const fields = [...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.\n const onKeyDown = (e: ReactKeyboardEvent<HTMLInputElement>) => {\n if (open && items.length && list.current?.onKeyDown(e.nativeEvent)) {\n e.preventDefault()\n return\n }\n const input = e.currentTarget\n if (e.key === 'Escape' && open) {\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 editor.commands.focus(getPos()! + node.nodeSize - 1)\n }\n }\n\n return (\n <NodeViewWrapper\n className={cn('transcript-segment my-2', selected && 'is-selected rounded-md ring-1 ring-primary')}\n data-type=\"transcript-segment\"\n data-id={node.attrs.id || undefined}\n >\n <div className=\"grid grid-cols-[5.5rem_1fr] items-start gap-2\">\n <div className=\"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 className=\"w-full truncate rounded bg-transparent px-1 text-sm text-muted-foreground outline-none placeholder:text-muted-foreground/60 focus:bg-muted focus:text-foreground\"\n onFocus={() => {\n focused.current = true\n setReadonly(!editor.isEditable)\n }}\n onBlur={() => {\n focused.current = false\n closeList()\n void commit()\n }}\n onMouseDown={() => openList('')}\n onChange={(e) => {\n setDraft(e.target.value)\n openList(e.target.value)\n }}\n onKeyDown={onKeyDown}\n />\n {open && items.length > 0 && (\n <div className=\"absolute top-full left-0 z-50 mt-1\">\n <SuggestionList ref={list} items={items} display={displayItem} command={pick} loading={false} loadingText=\"\" emptyText=\"\" />\n </div>\n )}\n </div>\n <NodeViewContent className=\"transcript-segment__text\" />\n </div>\n </NodeViewWrapper>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TranscriptSegmentView.js","names":[],"sources":["../../../../src/features/transcript/react/TranscriptSegmentView.tsx"],"sourcesContent":["import { NodeViewContent, NodeViewWrapper, type NodeViewProps } from '@tiptap/react'\nimport { type KeyboardEvent as ReactKeyboardEvent, useCallback, useEffect, useRef, useState } from 'react'\nimport { METADATA_LIST, type MetadataItem, metadataOf } from '../../../core'\nimport { SuggestionList, type SuggestionListHandle } from '../../../react/suggestion-renderer'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { formatSegmentTime } from '../segment-node'\n\n// Module-level so the list's memoised rows keep their identity across renders.\nconst displayItem = (item: unknown) => ({ label: (item as MetadataItem).label })\n\n/** The speaker segment: the speaker field (with a suggestion list of the `speakers` metadata) beside the editable text. */\nexport function TranscriptSegmentView({ node, editor, selected, updateAttributes, getPos }: NodeViewProps) {\n const { t } = useEditorI18n()\n const speaker = String(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.\n const [draft, setDraft] = useState(speaker)\n const focused = useRef(false)\n useEffect(() => {\n if (!focused.current) setDraft(speaker)\n }, [speaker])\n // `editor.isEditable` is not reactive, so a read-only editor is detected when the input gets focus.\n const [readonly, setReadonly] = useState(false)\n\n const [open, setOpen] = useState(false)\n const [items, setItems] = useState<MetadataItem[]>([])\n const list = useRef<SuggestionListHandle>(null)\n\n // Same loading as the composite tags: 150 ms after typing pauses, a newer search aborts the older one (sources may be remote).\n const timer = useRef<ReturnType<typeof setTimeout>>(undefined)\n const pending = useRef<AbortController>(undefined)\n const scheduleSearch = (query: string) => {\n clearTimeout(timer.current)\n timer.current = setTimeout(async () => {\n pending.current?.abort()\n const controller = pending.current = new AbortController()\n try {\n const found = await metadataOf(editor).search(METADATA_LIST.speakers, { query, editor, signal: controller.signal })\n if (!controller.signal.aborted) setItems(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 }\n useEffect(() => () => {\n clearTimeout(timer.current)\n pending.current?.abort()\n }, [])\n\n const openList = (query: string) => {\n if (!editor.isEditable) return\n setOpen(true)\n scheduleSearch(query)\n }\n\n const closeList = useCallback(() => {\n setOpen(false)\n clearTimeout(timer.current)\n pending.current?.abort()\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`). */\n const commit = async (name = draft.trim()) => {\n setDraft(name)\n if (readonly || name === speaker) 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(editor).search(METADATA_LIST.speakers, { query: name, editor }) : null\n updateAttributes({ speaker: name })\n if (!found) return\n if (!(await found).some(item => item.label.toLowerCase() === name.toLowerCase())) {\n metadataOf(editor).create(METADATA_LIST.speakers, { id: name, label: name })\n }\n }\n\n const pick = (item: unknown) => {\n closeList()\n void commit((item as MetadataItem).label)\n }\n\n /** The speaker field of the segment `step` places before/after this one (document order), if any. */\n const neighbourField = (input: HTMLInputElement, step: -1 | 1): HTMLInputElement | undefined => {\n const fields = [...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.\n const onKeyDown = (e: ReactKeyboardEvent<HTMLInputElement>) => {\n if (open && items.length && list.current?.onKeyDown(e.nativeEvent)) {\n e.preventDefault()\n return\n }\n const input = e.currentTarget\n if (e.key === 'Escape' && open) {\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 editor.commands.focus(getPos()! + 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). */\n const seek = () => {\n const start = node.attrs.start\n const pos = 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) editor.commands.seekSource?.(pos, start)\n }\n\n return (\n <NodeViewWrapper\n className={cn('transcript-segment my-2', selected && 'is-selected rounded-md ring-1 ring-primary')}\n data-type=\"transcript-segment\"\n data-id={node.attrs.id || undefined}\n >\n <div className=\"grid grid-cols-[5.5rem_1fr] items-start gap-2\">\n <div className=\"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 className=\"w-full truncate rounded bg-transparent px-1 text-sm text-muted-foreground outline-none placeholder:text-muted-foreground/60 focus:bg-muted focus:text-foreground\"\n onFocus={() => {\n focused.current = true\n setReadonly(!editor.isEditable)\n }}\n onBlur={() => {\n focused.current = false\n closeList()\n void commit()\n }}\n onMouseDown={() => openList('')}\n onChange={(e) => {\n setDraft(e.target.value)\n openList(e.target.value)\n }}\n onKeyDown={onKeyDown}\n />\n {node.attrs.start != null && (\n <button\n type=\"button\"\n className=\"transcript-segment__time block rounded px-1 text-xs tabular-nums text-muted-foreground hover:text-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 onMouseDown={e => e.preventDefault()}\n onClick={seek}\n >\n {formatSegmentTime(node.attrs.start)}\n </button>\n )}\n {open && items.length > 0 && (\n <div className=\"absolute top-full left-0 z-50 mt-1\">\n <SuggestionList ref={list} items={items} display={displayItem} command={pick} loading={false} loadingText=\"\" emptyText=\"\" />\n </div>\n )}\n </div>\n <NodeViewContent className=\"transcript-segment__text\" />\n </div>\n </NodeViewWrapper>\n )\n}\n"],"mappings":"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"}
@@ -6,6 +6,8 @@ declare module '@tiptap/core' {
6
6
  /** Turn the current block into a speaker segment (or back into a paragraph when it already is one). */
7
7
  toggleTranscriptSegment: (attrs?: {
8
8
  speaker?: string;
9
+ start?: number | null;
10
+ end?: number | null;
9
11
  }) => ReturnType;
10
12
  };
11
13
  }
@@ -7,6 +7,18 @@ import { canSplit } from "@tiptap/pm/transform";
7
7
  const UNNAMED_SPEAKER = "Sprecher";
8
8
  /** Longer bold paragraph openers („**Bitte beachten Sie Folgendes:** …“) are not treated as speaker labels. */
9
9
  const MAX_SPEAKER_LENGTH = 80;
10
+ /** A `data-start`/`data-end` attribute as seconds; null when missing or not a non-negative number. */
11
+ function timeAttr(value) {
12
+ if (value === null || value.trim() === "") return null;
13
+ const seconds = Number(value);
14
+ return Number.isFinite(seconds) && seconds >= 0 ? seconds : null;
15
+ }
16
+ /** `mm:ss` of a segment time in seconds (minutes keep counting past 59: `75:03`); '' for null / not a number. */
17
+ function formatSegmentTime(seconds) {
18
+ if (typeof seconds !== "number" || !Number.isFinite(seconds) || seconds < 0) return "";
19
+ const whole = Math.floor(seconds);
20
+ return `${String(Math.floor(whole / 60)).padStart(2, "0")}:${String(whole % 60).padStart(2, "0")}`;
21
+ }
10
22
  const TranscriptSegmentNode = Node.create({
11
23
  name: "transcriptSegment",
12
24
  priority: 101,
@@ -23,11 +35,23 @@ const TranscriptSegmentNode = Node.create({
23
35
  ABILITY.record
24
36
  ] },
25
37
  defining: true,
26
- addAttributes: () => ({ speaker: {
27
- default: "",
28
- parseHTML: (el) => el.getAttribute("data-speaker") ?? "",
29
- renderHTML: (attrs) => ({ "data-speaker": attrs.speaker })
30
- } }),
38
+ addAttributes: () => ({
39
+ speaker: {
40
+ default: "",
41
+ parseHTML: (el) => el.getAttribute("data-speaker") ?? "",
42
+ renderHTML: (attrs) => ({ "data-speaker": attrs.speaker })
43
+ },
44
+ start: {
45
+ default: null,
46
+ parseHTML: (el) => timeAttr(el.getAttribute("data-start")),
47
+ renderHTML: (attrs) => attrs.start == null ? {} : { "data-start": attrs.start }
48
+ },
49
+ end: {
50
+ default: null,
51
+ parseHTML: (el) => timeAttr(el.getAttribute("data-end")),
52
+ renderHTML: (attrs) => attrs.end == null ? {} : { "data-end": attrs.end }
53
+ }
54
+ }),
31
55
  parseHTML: () => [{ tag: "div[data-type=\"transcript-segment\"]" }],
32
56
  renderHTML: ({ HTMLAttributes }) => [
33
57
  "div",
@@ -36,7 +60,11 @@ const TranscriptSegmentNode = Node.create({
36
60
  ],
37
61
  renderMarkdown: (node, h) => `**${String(node.attrs?.speaker || UNNAMED_SPEAKER).replace(/[\\*_`[\]<>~]/g, "\\$&")}:** ${h.renderChildren(node.content ?? [])}`,
38
62
  addCommands() {
39
- return { toggleTranscriptSegment: (attrs = {}) => ({ editor, commands }) => editor.isActive(this.name) ? commands.setNode("paragraph") : commands.setNode(this.name, { speaker: attrs.speaker ?? lastSpeaker(editor.state.doc) }) };
63
+ return { toggleTranscriptSegment: (attrs = {}) => ({ editor, commands }) => editor.isActive(this.name) ? commands.setNode("paragraph") : commands.setNode(this.name, {
64
+ speaker: attrs.speaker ?? lastSpeaker(editor.state.doc),
65
+ start: attrs.start ?? null,
66
+ end: attrs.end ?? null
67
+ }) };
40
68
  },
41
69
  addKeyboardShortcuts() {
42
70
  return {
@@ -108,6 +136,6 @@ function speakerParagraphsToSegments(doc) {
108
136
  };
109
137
  }
110
138
  //#endregion
111
- export { TranscriptSegmentNode, speakerParagraphsToSegments };
139
+ export { TranscriptSegmentNode, formatSegmentTime, speakerParagraphsToSegments };
112
140
 
113
141
  //# sourceMappingURL=segment-node.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"segment-node.js","names":[],"sources":["../../../src/features/transcript/segment-node.ts"],"sourcesContent":["import { type JSONContent, mergeAttributes, Node } from '@tiptap/core'\nimport { canSplit } from '@tiptap/pm/transform'\nimport { ABILITY } from '../../core/schema'\nimport { lastSpeaker } from './speakers'\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n transcriptSegment: {\n /** Turn the current block into a speaker segment (or back into a paragraph when it already is one). */\n toggleTranscriptSegment: (attrs?: { speaker?: string }) => ReturnType\n }\n }\n}\n\n/** Markdown label for a segment without speaker; read back as an empty speaker so export → import round-trips. */\nconst UNNAMED_SPEAKER = 'Sprecher'\n/** Longer bold paragraph openers („**Bitte beachten Sie Folgendes:** …“) are not treated as speaker labels. */\nconst MAX_SPEAKER_LENGTH = 80\n\n// Schema-only node (no node view): shared by the client editor and the server-side Yjs seeding.\n// The client adds its node view in node-view.ts.\nexport const TranscriptSegmentNode = Node.create({\n name: 'transcriptSegment',\n // Above StarterKit's default (100) so this Enter / Mod-Shift-s keymap runs first,\n // below Paragraph (1000) so the paragraph stays the schema's default block.\n priority: 101,\n group: 'block',\n content: 'inline*',\n // A speaker turn is plain running text: no structural blocks or media inside it.\n abilities: {\n revokes: [ABILITY.headings, ABILITY.lists, ABILITY.blockquote, ABILITY.codeBlock, ABILITY.blockContent, ABILITY.composite, ABILITY.media, ABILITY.record],\n },\n defining: true,\n addAttributes: () => ({\n speaker: {\n default: '',\n parseHTML: (el) => el.getAttribute('data-speaker') ?? '',\n renderHTML: (attrs) => ({ 'data-speaker': attrs.speaker }),\n },\n }),\n parseHTML: () => [{ tag: 'div[data-type=\"transcript-segment\"]' }],\n renderHTML: ({ HTMLAttributes }) => ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'transcript-segment' }), 0],\n // Markdown export (editor.getMarkdown(), AI context/selection) keeps the speaker visible to the model as\n // `**Name:** text`. Import goes through speakerParagraphsToSegments() below, not parseMarkdown: marked yields\n // an ordinary paragraph token, and Paragraph (priority 1000) would always win that token over this node.\n // Backslash-escaped ASCII punctuation parses back to the literal character, so odd names survive the round-trip.\n // No trailing newline: the serializer separates blocks itself; extra blank lines re-import as empty paragraphs.\n renderMarkdown: (node, h) => `**${String(node.attrs?.speaker || UNNAMED_SPEAKER).replace(/[\\\\*_`[\\]<>~]/g, '\\\\$&')}:** ${h.renderChildren(node.content ?? [])}`,\n addCommands() {\n return {\n toggleTranscriptSegment: (attrs = {}) => ({ editor, commands }) =>\n editor.isActive(this.name)\n ? commands.setNode('paragraph')\n : commands.setNode(this.name, { speaker: attrs.speaker ?? lastSpeaker(editor.state.doc) }),\n }\n },\n addKeyboardShortcuts() {\n return {\n // Enter inside a segment splits it into two segments with the same speaker — at the start, middle\n // or end alike. `splitBlock` is not used: it creates the default block (paragraph) at the end and\n // re-types the first half at the start. Shift-Enter keeps StarterKit's hard break.\n Enter: () => {\n const { $from } = this.editor.state.selection\n if ($from.parent.type.name !== this.name) return false\n const type = $from.parent.type\n const speaker = String($from.parent.attrs.speaker ?? '')\n return this.editor.commands.command(({ tr, state, dispatch }) => {\n const marks = state.storedMarks ?? ($from.parentOffset ? $from.marks() : null)\n const after = [{ type, attrs: { speaker } }]\n if (!canSplit(state.doc, state.selection.from, 1, after)) return false\n if (dispatch) {\n if (!tr.selection.empty) tr.deleteSelection()\n tr.split(tr.selection.from, 1, after)\n if (marks) tr.ensureMarks(marks)\n tr.scrollIntoView()\n }\n return true\n })\n },\n 'Mod-Shift-s': () => this.editor.commands.toggleTranscriptSegment(),\n }\n },\n})\n\n/**\n * Speaker name of a speaker line, i.e. a paragraph whose first inline node is bold-only text `Name:`\n * (Markdown `**Name:** text`), or null. Bold elsewhere in the paragraph does not count.\n */\nfunction speakerOf(block: JSONContent): string | null {\n const first = block.content?.[0]\n if (block.type !== 'paragraph' || first?.type !== 'text' || !first.text?.endsWith(':')) return null\n if (first.marks?.length !== 1 || first.marks[0]!.type !== 'bold') return null\n const name = first.text.slice(0, -1).trim()\n if (!name || name.length > MAX_SPEAKER_LENGTH || name.includes('\\n')) return null\n return name\n}\n\n/**\n * Turns top-level speaker-line paragraphs of parsed Markdown into transcriptSegment nodes (the inverse of\n * renderMarkdown above); every other block is returned unchanged. Applied to AI results on both apply paths.\n * A label without text (`**Aufgaben:**` above a list) is a bold pseudo-heading, not an empty turn, and stays a paragraph.\n */\nexport function speakerParagraphsToSegments(doc: JSONContent): JSONContent {\n if (!doc.content) return doc\n return {\n ...doc,\n content: doc.content.map((block) => {\n const name = speakerOf(block)\n if (name === null) return block\n const content = block.content!.slice(1)\n if (content.every(node => node.type === 'text' && !node.text?.trim())) return block\n const next = content[0]\n // renderMarkdown puts exactly one space between label and text; drop only that one.\n if (next?.type === 'text' && next.text && /^\\s/.test(next.text)) {\n if (next.text.length === 1) content.shift()\n else content[0] = { ...next, text: next.text.slice(1) }\n }\n return { type: 'transcriptSegment', attrs: { speaker: name === UNNAMED_SPEAKER ? '' : name }, content }\n }),\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"segment-node.js","names":[],"sources":["../../../src/features/transcript/segment-node.ts"],"sourcesContent":["import { type JSONContent, mergeAttributes, Node } from '@tiptap/core'\nimport { canSplit } from '@tiptap/pm/transform'\nimport { ABILITY } from '../../core/schema'\nimport { lastSpeaker } from './speakers'\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n transcriptSegment: {\n /** Turn the current block into a speaker segment (or back into a paragraph when it already is one). */\n toggleTranscriptSegment: (attrs?: { speaker?: string, start?: number | null, end?: number | null }) => ReturnType\n }\n }\n}\n\n/** Markdown label for a segment without speaker; read back as an empty speaker so export → import round-trips. */\nconst UNNAMED_SPEAKER = 'Sprecher'\n/** Longer bold paragraph openers („**Bitte beachten Sie Folgendes:** …“) are not treated as speaker labels. */\nconst MAX_SPEAKER_LENGTH = 80\n\n/** A `data-start`/`data-end` attribute as seconds; null when missing or not a non-negative number. */\nfunction timeAttr(value: string | null): number | null {\n if (value === null || value.trim() === '') return null\n const seconds = Number(value)\n return Number.isFinite(seconds) && seconds >= 0 ? seconds : null\n}\n\n/** `mm:ss` of a segment time in seconds (minutes keep counting past 59: `75:03`); '' for null / not a number. */\nexport function formatSegmentTime(seconds: unknown): string {\n if (typeof seconds !== 'number' || !Number.isFinite(seconds) || seconds < 0) return ''\n const whole = Math.floor(seconds)\n return `${String(Math.floor(whole / 60)).padStart(2, '0')}:${String(whole % 60).padStart(2, '0')}`\n}\n\n// Schema-only node (no node view): shared by the client editor and the server-side Yjs seeding.\n// The client adds its node view in node-view.ts.\nexport const TranscriptSegmentNode = Node.create({\n name: 'transcriptSegment',\n // Above StarterKit's default (100) so this Enter / Mod-Shift-s keymap runs first,\n // below Paragraph (1000) so the paragraph stays the schema's default block.\n priority: 101,\n group: 'block',\n content: 'inline*',\n // A speaker turn is plain running text: no structural blocks or media inside it.\n abilities: {\n revokes: [ABILITY.headings, ABILITY.lists, ABILITY.blockquote, ABILITY.codeBlock, ABILITY.blockContent, ABILITY.composite, ABILITY.media, ABILITY.record],\n },\n defining: true,\n addAttributes: () => ({\n speaker: {\n default: '',\n parseHTML: (el) => el.getAttribute('data-speaker') ?? '',\n renderHTML: (attrs) => ({ 'data-speaker': attrs.speaker }),\n },\n // Seconds into the recording of the enclosing source-holding Baustein; null for a segment without timing.\n start: {\n default: null,\n parseHTML: el => timeAttr(el.getAttribute('data-start')),\n renderHTML: attrs => (attrs.start == null ? {} : { 'data-start': attrs.start }),\n },\n end: {\n default: null,\n parseHTML: el => timeAttr(el.getAttribute('data-end')),\n renderHTML: attrs => (attrs.end == null ? {} : { 'data-end': attrs.end }),\n },\n }),\n parseHTML: () => [{ tag: 'div[data-type=\"transcript-segment\"]' }],\n renderHTML: ({ HTMLAttributes }) => ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'transcript-segment' }), 0],\n // Markdown export (editor.getMarkdown(), AI context/selection) keeps the speaker visible to the model as\n // `**Name:** text`; `start`/`end` (seconds into the source recording) live in JSON/Yjs only and are not part of the\n // Markdown, so the Markdown output of a segment is the same with or without timestamps.\n // Import goes through speakerParagraphsToSegments() below, not parseMarkdown: marked yields\n // an ordinary paragraph token, and Paragraph (priority 1000) would always win that token over this node.\n // Backslash-escaped ASCII punctuation parses back to the literal character, so odd names survive the round-trip.\n // No trailing newline: the serializer separates blocks itself; extra blank lines re-import as empty paragraphs.\n renderMarkdown: (node, h) => `**${String(node.attrs?.speaker || UNNAMED_SPEAKER).replace(/[\\\\*_`[\\]<>~]/g, '\\\\$&')}:** ${h.renderChildren(node.content ?? [])}`,\n addCommands() {\n return {\n toggleTranscriptSegment: (attrs = {}) => ({ editor, commands }) =>\n editor.isActive(this.name)\n ? commands.setNode('paragraph')\n : commands.setNode(this.name, { speaker: attrs.speaker ?? lastSpeaker(editor.state.doc), start: attrs.start ?? null, end: attrs.end ?? null }),\n }\n },\n addKeyboardShortcuts() {\n return {\n // Enter inside a segment splits it into two segments with the same speaker — at the start, middle\n // or end alike. `splitBlock` is not used: it creates the default block (paragraph) at the end and\n // re-types the first half at the start. Shift-Enter keeps StarterKit's hard break.\n Enter: () => {\n const { $from } = this.editor.state.selection\n if ($from.parent.type.name !== this.name) return false\n const type = $from.parent.type\n const speaker = String($from.parent.attrs.speaker ?? '')\n return this.editor.commands.command(({ tr, state, dispatch }) => {\n const marks = state.storedMarks ?? ($from.parentOffset ? $from.marks() : null)\n const after = [{ type, attrs: { speaker } }]\n if (!canSplit(state.doc, state.selection.from, 1, after)) return false\n if (dispatch) {\n if (!tr.selection.empty) tr.deleteSelection()\n tr.split(tr.selection.from, 1, after)\n if (marks) tr.ensureMarks(marks)\n tr.scrollIntoView()\n }\n return true\n })\n },\n 'Mod-Shift-s': () => this.editor.commands.toggleTranscriptSegment(),\n }\n },\n})\n\n/**\n * Speaker name of a speaker line, i.e. a paragraph whose first inline node is bold-only text `Name:`\n * (Markdown `**Name:** text`), or null. Bold elsewhere in the paragraph does not count.\n */\nfunction speakerOf(block: JSONContent): string | null {\n const first = block.content?.[0]\n if (block.type !== 'paragraph' || first?.type !== 'text' || !first.text?.endsWith(':')) return null\n if (first.marks?.length !== 1 || first.marks[0]!.type !== 'bold') return null\n const name = first.text.slice(0, -1).trim()\n if (!name || name.length > MAX_SPEAKER_LENGTH || name.includes('\\n')) return null\n return name\n}\n\n/**\n * Turns top-level speaker-line paragraphs of parsed Markdown into transcriptSegment nodes (the inverse of\n * renderMarkdown above); every other block is returned unchanged. Applied to AI results on both apply paths.\n * A label without text (`**Aufgaben:**` above a list) is a bold pseudo-heading, not an empty turn, and stays a paragraph.\n */\nexport function speakerParagraphsToSegments(doc: JSONContent): JSONContent {\n if (!doc.content) return doc\n return {\n ...doc,\n content: doc.content.map((block) => {\n const name = speakerOf(block)\n if (name === null) return block\n const content = block.content!.slice(1)\n if (content.every(node => node.type === 'text' && !node.text?.trim())) return block\n const next = content[0]\n // renderMarkdown puts exactly one space between label and text; drop only that one.\n if (next?.type === 'text' && next.text && /^\\s/.test(next.text)) {\n if (next.text.length === 1) content.shift()\n else content[0] = { ...next, text: next.text.slice(1) }\n }\n return { type: 'transcriptSegment', attrs: { speaker: name === UNNAMED_SPEAKER ? '' : name }, content }\n }),\n }\n}\n"],"mappings":"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"}