@skaile/forge-editor 5.0.2 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/code/CodeSurface.vue.d.ts +3 -3
  3. package/dist/code/index.js +1 -1
  4. package/dist/core/toolbar-fit.js +99 -0
  5. package/dist/core/toolbar-fit.js.map +1 -0
  6. package/dist/document/DocumentSurface.js +1 -1
  7. package/dist/document/DocumentSurface.vue.d.ts +3 -3
  8. package/dist/features/ai/agent-ops.js +1 -1
  9. package/dist/features/ai/agent-ops.js.map +1 -1
  10. package/dist/features/ai/document-chat.d.ts +3 -2
  11. package/dist/features/ai/document-chat.js +6 -4
  12. package/dist/features/ai/document-chat.js.map +1 -1
  13. package/dist/features/ai/protocol/block-lines.d.ts +2 -1
  14. package/dist/features/ai/protocol/block-lines.js +2 -1
  15. package/dist/features/ai/protocol/block-lines.js.map +1 -1
  16. package/dist/features/composites/components/CompositeInspector.js +8 -8
  17. package/dist/features/composites/vue.js +1 -1
  18. package/dist/features/themes/index.d.ts +2 -2
  19. package/dist/features/themes/index.js +2 -2
  20. package/dist/features/themes/theme-runtime.d.ts +15 -4
  21. package/dist/features/themes/theme-runtime.js +76 -11
  22. package/dist/features/themes/theme-runtime.js.map +1 -1
  23. package/dist/markdown/MarkdownSurface.vue.d.ts +5 -5
  24. package/dist/react/EditorToolbar.d.ts +5 -1
  25. package/dist/react/EditorToolbar.js +76 -18
  26. package/dist/react/EditorToolbar.js.map +1 -1
  27. package/dist/server/document-blocks.js +5 -1
  28. package/dist/server/document-blocks.js.map +1 -1
  29. package/dist/styles/themes.css +10 -8
  30. package/dist/tree/DocumentTree.js +1 -1
  31. package/dist/tree/use-tree-model.js +1 -1
  32. package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
  33. package/dist/vue/SimpleEditor.vue.d.ts +4 -4
  34. package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
  35. package/dist/vue/components/EditorToolbar.js +81 -9
  36. package/dist/vue/components/EditorToolbar.js.map +1 -1
  37. package/dist/vue/components/EditorToolbar.vue.d.ts +7 -1
  38. package/dist/vue/compose.js +1 -1
  39. package/dist/vue/index.js +1 -1
  40. package/package.json +1 -1
@@ -5,6 +5,27 @@ const THEME_SCOPE_CLASS = "obsidian-theme";
5
5
  /** Marks the single `<style>` element in `document.head` that carries the active theme. */
6
6
  const STYLE_ATTRIBUTE = "data-tiptap-theme";
7
7
  const runtimes = /* @__PURE__ */ new Map();
8
+ const pins = /* @__PURE__ */ new Map();
9
+ function pinsOf(key) {
10
+ let entry = pins.get(key);
11
+ if (!entry) {
12
+ entry = {
13
+ stack: [],
14
+ changed: /* @__PURE__ */ new Set()
15
+ };
16
+ pins.set(key, entry);
17
+ }
18
+ return entry;
19
+ }
20
+ function dropPinsIfIdle(key) {
21
+ const entry = pins.get(key);
22
+ if (entry && !entry.stack.length && !entry.changed.size) pins.delete(key);
23
+ }
24
+ /** The theme the app shows: the most recent pin, else the setting. */
25
+ function effectiveTheme(host) {
26
+ const top = pins.get(host.settings)?.stack.at(-1);
27
+ return top ? top.id : host.settings.get().appearance.theme;
28
+ }
8
29
  const PREFERS_DARK = "(prefers-color-scheme: dark)";
9
30
  /** Whether the app is in dark mode: `html.dark` (Nuxt colour mode), `html.light`, else the system preference. */
10
31
  function readDark() {
@@ -80,14 +101,31 @@ function createRuntime(ctx) {
80
101
  applyRootClasses();
81
102
  }
82
103
  }
104
+ let stopped = false;
83
105
  if (typeof document !== "undefined") {
84
- select(ctx.settings.get().appearance.theme);
85
- let lastTheme = ctx.settings.get().appearance.theme;
86
- stopTheme = ctx.settings.subscribe((value) => {
87
- if (value.appearance.theme === lastTheme) return;
88
- lastTheme = value.appearance.theme;
89
- select(lastTheme);
90
- });
106
+ let lastTheme = effectiveTheme(ctx);
107
+ select(lastTheme);
108
+ let queued = false;
109
+ const reselect = () => {
110
+ if (queued) return;
111
+ queued = true;
112
+ queueMicrotask(() => {
113
+ queued = false;
114
+ if (stopped) return;
115
+ const next = effectiveTheme(ctx);
116
+ if (next === lastTheme) return;
117
+ lastTheme = next;
118
+ select(next);
119
+ });
120
+ };
121
+ const stopSettings = ctx.settings.subscribe(reselect);
122
+ const appPins = pinsOf(ctx.settings);
123
+ appPins.changed.add(reselect);
124
+ stopTheme = () => {
125
+ stopSettings();
126
+ appPins.changed.delete(reselect);
127
+ dropPinsIfIdle(ctx.settings);
128
+ };
91
129
  applyRootClasses();
92
130
  const onDarkChange = () => {
93
131
  const next = readDark();
@@ -109,6 +147,7 @@ function createRuntime(ctx) {
109
147
  };
110
148
  }
111
149
  return () => {
150
+ stopped = true;
112
151
  latest++;
113
152
  stopTheme?.();
114
153
  teardown?.();
@@ -116,9 +155,10 @@ function createRuntime(ctx) {
116
155
  };
117
156
  }
118
157
  /**
119
- * The theme runtime of the app `ctx` belongs to: applies `settings.appearance.theme` (fetches the stylesheet through
120
- * `ctx.backend.themes`, scopes it, keeps one `<style data-tiptap-theme>` in the document head, puts `obsidian-theme theme-dark|theme-light`
121
- * on `<html>`) and follows the app's colour mode.
158
+ * The theme runtime of the app `ctx` belongs to: applies `settings.appearance.theme` (or the most recent
159
+ * `pinObsidianTheme`), fetches the stylesheet through `ctx.backend.themes`, scopes it, keeps one
160
+ * `<style data-tiptap-theme>` in the document head, puts `obsidian-theme theme-dark|theme-light` on `<html>` and follows
161
+ * the app's colour mode.
122
162
  * Reference-counted per app: the first call starts the runtime, every call returns an idempotent release, the last release
123
163
  * stops it (removing the style element and the root classes). Surfaces of one app share a single runtime.
124
164
  */
@@ -143,7 +183,32 @@ function startObsidianTheme(ctx) {
143
183
  if (runtimes.get(key) === held) runtimes.delete(key);
144
184
  };
145
185
  }
186
+ /**
187
+ * Shows theme `id` (`null` = the standard look) instead of the user's `settings.appearance.theme` until the returned
188
+ * release runs; the setting itself is not touched, so nothing is persisted and the rest of the app keeps the user's
189
+ * choice. For a page or view with a look of its own. Pins stack per app: the most recent one wins, releasing it falls
190
+ * back to the one before, the last release back to the setting. A pin holds the app's theme runtime (`startObsidianTheme`),
191
+ * so it also applies on a page without a themes-feature surface. The release is idempotent.
192
+ */
193
+ function pinObsidianTheme(host, id) {
194
+ const key = host.settings;
195
+ const entry = { id };
196
+ const appPins = pinsOf(key);
197
+ appPins.stack.push(entry);
198
+ for (const wake of appPins.changed) wake();
199
+ const releaseRuntime = startObsidianTheme(host);
200
+ let released = false;
201
+ return () => {
202
+ if (released) return;
203
+ released = true;
204
+ const index = appPins.stack.indexOf(entry);
205
+ if (index !== -1) appPins.stack.splice(index, 1);
206
+ for (const wake of appPins.changed) wake();
207
+ releaseRuntime();
208
+ dropPinsIfIdle(key);
209
+ };
210
+ }
146
211
  //#endregion
147
- export { THEME_SCOPE_CLASS, startObsidianTheme };
212
+ export { THEME_SCOPE_CLASS, pinObsidianTheme, startObsidianTheme };
148
213
 
149
214
  //# sourceMappingURL=theme-runtime.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"theme-runtime.js","names":[],"sources":["../../../src/features/themes/theme-runtime.ts"],"sourcesContent":["import type { FeatureContext } from '../../core/feature'\nimport { scopeThemeCss } from './scope-css'\n\n/** Class that scopes a theme's rules (`scopeThemeCss`) and the bridge stylesheet; the runtime puts it on `<html>`. */\nexport const THEME_SCOPE_CLASS = 'obsidian-theme'\n\n/** Marks the single `<style>` element in `document.head` that carries the active theme. */\nconst STYLE_ATTRIBUTE = 'data-tiptap-theme'\n\n// One runtime per app (keyed by the app's settings store): every surface of the page shares the style element and the\n// cache. Reference-counted: the last release stops it.\nconst runtimes = new Map<object, { count: number, stop: () => void }>()\n\nconst PREFERS_DARK = '(prefers-color-scheme: dark)'\n\n/** Whether the app is in dark mode: `html.dark` (Nuxt colour mode), `html.light`, else the system preference. */\nfunction readDark(): boolean {\n if (typeof document === 'undefined') return false\n const classes = document.documentElement.classList\n if (classes.contains('dark')) return true\n if (classes.contains('light')) return false\n return typeof matchMedia === 'function' && matchMedia(PREFERS_DARK).matches\n}\n\nfunction createRuntime(ctx: FeatureContext): () => void {\n let dark = readDark()\n /** The theme whose style is in the document; null = standard look. */\n let applied: string | null = null\n const cache = new Map<string, string>()\n let style: HTMLStyleElement | undefined\n let latest = 0\n let teardown: (() => void) | undefined\n let stopTheme: (() => void) | undefined\n\n /** `obsidian-theme theme-dark|theme-light` on `<html>` while a theme is applied; none of the three otherwise (standard look). */\n function applyRootClasses() {\n if (typeof document === 'undefined') return\n const root = document.documentElement.classList\n const on = applied !== null\n root.toggle(THEME_SCOPE_CLASS, on)\n root.toggle('theme-dark', on && dark)\n root.toggle('theme-light', on && !dark)\n }\n\n function removeRootClasses() {\n if (typeof document === 'undefined') return\n document.documentElement.classList.remove(THEME_SCOPE_CLASS, 'theme-dark', 'theme-light')\n }\n\n function removeStyle() {\n style?.remove()\n style = undefined\n }\n\n function showStyle(css: string) {\n if (!style) {\n style = document.createElement('style')\n style.setAttribute(STYLE_ATTRIBUTE, '')\n document.head.appendChild(style)\n }\n style.textContent = css\n }\n\n async function load(id: string): Promise<string> {\n const cached = cache.get(id)\n if (cached !== undefined) return cached\n const themes = ctx.backend.themes\n if (!themes) throw new Error(`@skaile/forge-editor: backend \"${ctx.backend.name}\" has no themes capability`)\n const css = await themes.css(id)\n const scoped = scopeThemeCss(css, `.${THEME_SCOPE_CLASS}`)\n cache.set(id, scoped)\n return scoped\n }\n\n async function select(id: string | null) {\n const ticket = ++latest\n if (id === null) {\n removeStyle()\n applied = null\n applyRootClasses()\n return\n }\n try {\n const css = await load(id)\n if (ticket !== latest) return\n showStyle(css)\n applied = id\n applyRootClasses()\n }\n catch (error) {\n if (ticket !== latest) return\n // The stored setting stays untouched: the theme may come back (host restored it), the session shows the standard look.\n console.warn(`tiptap-editor: Theme „${id}“ konnte nicht geladen werden, es gilt das Standard-Aussehen`, error)\n removeStyle()\n applied = null\n applyRootClasses()\n }\n }\n\n if (typeof document !== 'undefined') {\n void select(ctx.settings.get().appearance.theme)\n let lastTheme = ctx.settings.get().appearance.theme\n stopTheme = ctx.settings.subscribe((value) => {\n if (value.appearance.theme === lastTheme) return\n lastTheme = value.appearance.theme\n void select(lastTheme)\n })\n applyRootClasses()\n const onDarkChange = () => {\n const next = readDark()\n if (next === dark) return\n dark = next\n applyRootClasses()\n }\n const observer = new MutationObserver(onDarkChange)\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })\n const media = typeof matchMedia === 'function' ? matchMedia(PREFERS_DARK) : undefined\n media?.addEventListener('change', onDarkChange)\n teardown = () => {\n observer.disconnect()\n media?.removeEventListener('change', onDarkChange)\n removeStyle()\n }\n }\n\n return () => {\n latest++\n stopTheme?.()\n teardown?.()\n removeRootClasses()\n }\n}\n\n/**\n * The theme runtime of the app `ctx` belongs to: applies `settings.appearance.theme` (fetches the stylesheet through\n * `ctx.backend.themes`, scopes it, keeps one `<style data-tiptap-theme>` in the document head, puts `obsidian-theme theme-dark|theme-light`\n * on `<html>`) and follows the app's colour mode.\n * Reference-counted per app: the first call starts the runtime, every call returns an idempotent release, the last release\n * stops it (removing the style element and the root classes). Surfaces of one app share a single runtime.\n */\nexport function startObsidianTheme(ctx: FeatureContext): () => void {\n const key: object = ctx.settings\n let entry = runtimes.get(key)\n if (!entry) {\n entry = { count: 0, stop: createRuntime(ctx) }\n runtimes.set(key, entry)\n }\n entry.count++\n const held = entry\n let released = false\n return () => {\n if (released) return\n released = true\n if (--held.count > 0) return\n held.stop()\n if (runtimes.get(key) === held) runtimes.delete(key)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"theme-runtime.js","names":[],"sources":["../../../src/features/themes/theme-runtime.ts"],"sourcesContent":["import type { FeatureContext } from '../../core/feature'\nimport { scopeThemeCss } from './scope-css'\n\n/** Class that scopes a theme's rules (`scopeThemeCss`) and the bridge stylesheet; the runtime puts it on `<html>`. */\nexport const THEME_SCOPE_CLASS = 'obsidian-theme'\n\n/** Marks the single `<style>` element in `document.head` that carries the active theme. */\nconst STYLE_ATTRIBUTE = 'data-tiptap-theme'\n\n// One runtime per app (keyed by the app's settings store): every surface of the page shares the style element and the\n// cache. Reference-counted: the last release stops it.\nconst runtimes = new Map<object, { count: number, stop: () => void }>()\n\n/** What the runtime reads of a surface's `FeatureContext` (an app's `EditorContext` has both, too). */\nexport type ThemeHost = Pick<FeatureContext, 'settings' | 'backend'>\n\n/** The pins of one app (keyed like `runtimes`): the last one names the theme shown; `changed` wakes the app's runtime. */\ninterface Pins {\n stack: { id: string | null }[]\n changed: Set<() => void>\n}\nconst pins = new Map<object, Pins>()\n\nfunction pinsOf(key: object): Pins {\n let entry = pins.get(key)\n if (!entry) {\n entry = { stack: [], changed: new Set() }\n pins.set(key, entry)\n }\n return entry\n}\n\nfunction dropPinsIfIdle(key: object) {\n const entry = pins.get(key)\n if (entry && !entry.stack.length && !entry.changed.size) pins.delete(key)\n}\n\n/** The theme the app shows: the most recent pin, else the setting. */\nfunction effectiveTheme(host: ThemeHost): string | null {\n const top = pins.get(host.settings)?.stack.at(-1)\n return top ? top.id : host.settings.get().appearance.theme\n}\n\nconst PREFERS_DARK = '(prefers-color-scheme: dark)'\n\n/** Whether the app is in dark mode: `html.dark` (Nuxt colour mode), `html.light`, else the system preference. */\nfunction readDark(): boolean {\n if (typeof document === 'undefined') return false\n const classes = document.documentElement.classList\n if (classes.contains('dark')) return true\n if (classes.contains('light')) return false\n return typeof matchMedia === 'function' && matchMedia(PREFERS_DARK).matches\n}\n\nfunction createRuntime(ctx: ThemeHost): () => void {\n let dark = readDark()\n /** The theme whose style is in the document; null = standard look. */\n let applied: string | null = null\n const cache = new Map<string, string>()\n let style: HTMLStyleElement | undefined\n let latest = 0\n let teardown: (() => void) | undefined\n let stopTheme: (() => void) | undefined\n\n /** `obsidian-theme theme-dark|theme-light` on `<html>` while a theme is applied; none of the three otherwise (standard look). */\n function applyRootClasses() {\n if (typeof document === 'undefined') return\n const root = document.documentElement.classList\n const on = applied !== null\n root.toggle(THEME_SCOPE_CLASS, on)\n root.toggle('theme-dark', on && dark)\n root.toggle('theme-light', on && !dark)\n }\n\n function removeRootClasses() {\n if (typeof document === 'undefined') return\n document.documentElement.classList.remove(THEME_SCOPE_CLASS, 'theme-dark', 'theme-light')\n }\n\n function removeStyle() {\n style?.remove()\n style = undefined\n }\n\n function showStyle(css: string) {\n if (!style) {\n style = document.createElement('style')\n style.setAttribute(STYLE_ATTRIBUTE, '')\n document.head.appendChild(style)\n }\n style.textContent = css\n }\n\n async function load(id: string): Promise<string> {\n const cached = cache.get(id)\n if (cached !== undefined) return cached\n const themes = ctx.backend.themes\n if (!themes) throw new Error(`@skaile/forge-editor: backend \"${ctx.backend.name}\" has no themes capability`)\n const css = await themes.css(id)\n const scoped = scopeThemeCss(css, `.${THEME_SCOPE_CLASS}`)\n cache.set(id, scoped)\n return scoped\n }\n\n async function select(id: string | null) {\n const ticket = ++latest\n if (id === null) {\n removeStyle()\n applied = null\n applyRootClasses()\n return\n }\n try {\n const css = await load(id)\n if (ticket !== latest) return\n showStyle(css)\n applied = id\n applyRootClasses()\n }\n catch (error) {\n if (ticket !== latest) return\n // The stored setting stays untouched: the theme may come back (host restored it), the session shows the standard look.\n console.warn(`tiptap-editor: Theme „${id}“ konnte nicht geladen werden, es gilt das Standard-Aussehen`, error)\n removeStyle()\n applied = null\n applyRootClasses()\n }\n }\n\n let stopped = false\n if (typeof document !== 'undefined') {\n let lastTheme = effectiveTheme(ctx)\n void select(lastTheme)\n // Coalesced: a page that releases its pin and the next page that pins its own in the same task (a route change) load\n // only the second theme, never the setting's one in between.\n let queued = false\n const reselect = () => {\n if (queued) return\n queued = true\n queueMicrotask(() => {\n queued = false\n if (stopped) return\n const next = effectiveTheme(ctx)\n if (next === lastTheme) return\n lastTheme = next\n void select(next)\n })\n }\n const stopSettings = ctx.settings.subscribe(reselect)\n const appPins = pinsOf(ctx.settings)\n appPins.changed.add(reselect)\n stopTheme = () => {\n stopSettings()\n appPins.changed.delete(reselect)\n dropPinsIfIdle(ctx.settings)\n }\n applyRootClasses()\n const onDarkChange = () => {\n const next = readDark()\n if (next === dark) return\n dark = next\n applyRootClasses()\n }\n const observer = new MutationObserver(onDarkChange)\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })\n const media = typeof matchMedia === 'function' ? matchMedia(PREFERS_DARK) : undefined\n media?.addEventListener('change', onDarkChange)\n teardown = () => {\n observer.disconnect()\n media?.removeEventListener('change', onDarkChange)\n removeStyle()\n }\n }\n\n return () => {\n stopped = true\n latest++\n stopTheme?.()\n teardown?.()\n removeRootClasses()\n }\n}\n\n/**\n * The theme runtime of the app `ctx` belongs to: applies `settings.appearance.theme` (or the most recent\n * `pinObsidianTheme`), fetches the stylesheet through `ctx.backend.themes`, scopes it, keeps one\n * `<style data-tiptap-theme>` in the document head, puts `obsidian-theme theme-dark|theme-light` on `<html>` and follows\n * the app's colour mode.\n * Reference-counted per app: the first call starts the runtime, every call returns an idempotent release, the last release\n * stops it (removing the style element and the root classes). Surfaces of one app share a single runtime.\n */\nexport function startObsidianTheme(ctx: ThemeHost): () => void {\n const key: object = ctx.settings\n let entry = runtimes.get(key)\n if (!entry) {\n entry = { count: 0, stop: createRuntime(ctx) }\n runtimes.set(key, entry)\n }\n entry.count++\n const held = entry\n let released = false\n return () => {\n if (released) return\n released = true\n if (--held.count > 0) return\n held.stop()\n if (runtimes.get(key) === held) runtimes.delete(key)\n }\n}\n\n/**\n * Shows theme `id` (`null` = the standard look) instead of the user's `settings.appearance.theme` until the returned\n * release runs; the setting itself is not touched, so nothing is persisted and the rest of the app keeps the user's\n * choice. For a page or view with a look of its own. Pins stack per app: the most recent one wins, releasing it falls\n * back to the one before, the last release back to the setting. A pin holds the app's theme runtime (`startObsidianTheme`),\n * so it also applies on a page without a themes-feature surface. The release is idempotent.\n */\nexport function pinObsidianTheme(host: ThemeHost, id: string | null): () => void {\n const key: object = host.settings\n const entry = { id }\n const appPins = pinsOf(key)\n appPins.stack.push(entry)\n for (const wake of appPins.changed) wake()\n const releaseRuntime = startObsidianTheme(host)\n let released = false\n return () => {\n if (released) return\n released = true\n const index = appPins.stack.indexOf(entry)\n if (index !== -1) appPins.stack.splice(index, 1)\n for (const wake of appPins.changed) wake()\n releaseRuntime()\n dropPinsIfIdle(key)\n }\n}\n"],"mappings":"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"}
@@ -41,29 +41,29 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
41
41
  settingsOpen: import("vue").WritableComputedRef<boolean, boolean>;
42
42
  settingsTab: import("vue").WritableComputedRef<string | undefined, string | undefined>;
43
43
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
44
- "update:modelValue": (markdown: string) => any;
45
- ready: (editor: Editor) => any;
46
44
  "update:sidebarOpen": (value: boolean) => any;
47
45
  "update:commentsOpen": (value: boolean) => any;
46
+ ready: (editor: Editor) => any;
47
+ "update:modelValue": (markdown: string) => any;
48
48
  metadataCreate: (list: string, item: MetadataItem) => any;
49
49
  contentError: (error: Error) => any;
50
50
  settingsOpen: (tab?: string | undefined) => any;
51
51
  frontmatter: (frontmatter: string | null) => any;
52
52
  diagnostics: (diagnostics: MarkdownDiagnostic[]) => any;
53
53
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
54
- "onUpdate:modelValue"?: ((markdown: string) => any) | undefined;
55
- onReady?: ((editor: Editor) => any) | undefined;
56
54
  "onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
57
55
  "onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
56
+ onReady?: ((editor: Editor) => any) | undefined;
57
+ "onUpdate:modelValue"?: ((markdown: string) => any) | undefined;
58
58
  onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
59
59
  onContentError?: ((error: Error) => any) | undefined;
60
60
  onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
61
61
  onFrontmatter?: ((frontmatter: string | null) => any) | undefined;
62
62
  onDiagnostics?: ((diagnostics: MarkdownDiagnostic[]) => any) | undefined;
63
63
  }>, {
64
- modelValue: string;
65
64
  editable: boolean;
66
65
  settingsUi: "internal" | "external";
66
+ modelValue: string;
67
67
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
68
68
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
69
69
  declare const _default: typeof __VLS_export;
@@ -5,9 +5,13 @@ import { Editor } from "@tiptap/core";
5
5
  * `onReady`); the preset registered for that instance (`editorEntryOf`) supplies the groups. Without an editor (not
6
6
  * mounted yet, or read-only by the host's choice) it renders no groups. A destroyed editor counts as none: a host still
7
7
  * holds the old instance for the render in which its keyed surface remounts. Never reads a global active editor.
8
+ * `overflow`: `wrap` (default) breaks onto further lines; `collapse` keeps one line, and the groups that do not fit
9
+ * collapse from the end into one dropdown each (`component` commands stay beside it). The row then takes the width its
10
+ * container gives it.
8
11
  */
9
- export declare function EditorToolbar({ editor: given }: {
12
+ export declare function EditorToolbar({ editor: given, overflow }: {
10
13
  editor?: Editor;
14
+ overflow?: 'wrap' | 'collapse';
11
15
  }): import("react").JSX.Element;
12
16
  //#endregion
13
17
  //# sourceMappingURL=EditorToolbar.d.ts.map
@@ -9,9 +9,10 @@ import { Button } from "./ui/button.js";
9
9
  import { shortcutLabel } from "./key-labels.js";
10
10
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip.js";
11
11
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "./ui/dropdown-menu.js";
12
+ import { collapsedGroupMenu, fitToolbar } from "../core/toolbar-fit.js";
12
13
  import { Separator } from "./ui/separator.js";
13
- import { useState } from "react";
14
- import { jsx, jsxs } from "react/jsx-runtime";
14
+ import { useLayoutEffect, useRef, useState } from "react";
15
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
15
16
  import { useEditorState } from "@tiptap/react";
16
17
  //#region src/react/EditorToolbar.tsx
17
18
  const NO_EDITOR = {
@@ -35,8 +36,11 @@ function groupsOf(ctx, preset) {
35
36
  * `onReady`); the preset registered for that instance (`editorEntryOf`) supplies the groups. Without an editor (not
36
37
  * mounted yet, or read-only by the host's choice) it renders no groups. A destroyed editor counts as none: a host still
37
38
  * holds the old instance for the render in which its keyed surface remounts. Never reads a global active editor.
39
+ * `overflow`: `wrap` (default) breaks onto further lines; `collapse` keeps one line, and the groups that do not fit
40
+ * collapse from the end into one dropdown each (`component` commands stay beside it). The row then takes the width its
41
+ * container gives it.
38
42
  */
39
- function EditorToolbar({ editor: given }) {
43
+ function EditorToolbar({ editor: given, overflow = "wrap" }) {
40
44
  const editor = given?.isDestroyed ? void 0 : given;
41
45
  const app = useEditorContext();
42
46
  const { t } = useEditorI18n();
@@ -64,26 +68,80 @@ function EditorToolbar({ editor: given }) {
64
68
  state: editor ? commandState(editor, item, "selection") : NO_EDITOR
65
69
  })).filter(({ state: itemState }) => itemState.visible)
66
70
  })).filter(({ items }) => items.length > 0);
71
+ const row = useRef(null);
72
+ const fit = useRef(void 0);
73
+ const [expandedCount, setExpandedCount] = useState(Number.POSITIVE_INFINITY);
74
+ useLayoutEffect(() => {
75
+ if (overflow !== "collapse" || !row.current) return;
76
+ const current = fitToolbar(row.current, setExpandedCount);
77
+ fit.current = current;
78
+ return () => {
79
+ current.dispose();
80
+ fit.current = void 0;
81
+ setExpandedCount(Number.POSITIVE_INFINITY);
82
+ };
83
+ }, [overflow]);
84
+ useLayoutEffect(() => fit.current?.refresh());
85
+ const collapse = overflow === "collapse";
67
86
  return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx("div", {
68
- className: "flex flex-wrap items-center gap-1.5",
87
+ ref: row,
88
+ className: cn("flex items-center gap-1.5", collapse ? "min-w-0 flex-nowrap overflow-hidden" : "flex-wrap"),
69
89
  role: "toolbar",
70
90
  "aria-label": t("toolbar.label"),
71
- children: groups.map(({ group, items }, index) => /* @__PURE__ */ jsxs("div", {
72
- className: "contents",
73
- children: [index > 0 && /* @__PURE__ */ jsx(Separator, {
74
- orientation: "vertical",
75
- className: "mx-2 h-6 data-[orientation=vertical]:h-6"
76
- }), /* @__PURE__ */ jsx("div", {
77
- className: "flex items-center gap-0.5",
78
- children: items.map(({ item, state }) => "component" in item ? /* @__PURE__ */ jsx(item.component, { editor }, item.id) : /* @__PURE__ */ jsx(ToolbarButton, {
79
- item,
80
- editor,
81
- state
82
- }, item.id))
83
- })]
84
- }, group.id))
91
+ children: groups.map(({ group, items }, index) => {
92
+ const runnable = items.filter(({ item }) => !("component" in item));
93
+ const collapsed = collapse && index >= expandedCount && runnable.length > 0;
94
+ return /* @__PURE__ */ jsxs("div", {
95
+ className: collapse ? "flex shrink-0 items-center gap-1.5" : "contents",
96
+ "data-toolbar-group": group.id,
97
+ "data-items": runnable.length,
98
+ "data-collapsed": collapsed ? "" : void 0,
99
+ children: [index > 0 && /* @__PURE__ */ jsx(Separator, {
100
+ orientation: "vertical",
101
+ className: "mx-2 h-6 data-[orientation=vertical]:h-6"
102
+ }), /* @__PURE__ */ jsx("div", {
103
+ className: "flex items-center gap-0.5",
104
+ children: collapsed ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(CollapsedGroup, {
105
+ label: group.labelKey ? t(group.labelKey) : group.id,
106
+ icon: runnable[0].item.icon,
107
+ enabled: runnable.some(({ state: itemState }) => itemState.enabled),
108
+ menu: () => collapsedGroupMenu(items.map(({ item, state: itemState }) => ({
109
+ cmd: item,
110
+ state: itemState
111
+ })), editor)
112
+ }), items.map(({ item }) => "component" in item && /* @__PURE__ */ jsx(item.component, { editor }, item.id))] }) : items.map(({ item, state }) => "component" in item ? /* @__PURE__ */ jsx(item.component, { editor }, item.id) : /* @__PURE__ */ jsx(ToolbarButton, {
113
+ item,
114
+ editor,
115
+ state
116
+ }, item.id))
117
+ })]
118
+ }, group.id);
119
+ })
85
120
  }) });
86
121
  }
122
+ /** A group collapsed to one dropdown: the first command's icon, a chevron, the group's name as tooltip. */
123
+ function CollapsedGroup({ label, icon, enabled, menu }) {
124
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
125
+ asChild: true,
126
+ children: /* @__PURE__ */ jsx(DropdownMenuTrigger, {
127
+ asChild: true,
128
+ children: /* @__PURE__ */ jsxs(Button, {
129
+ variant: "ghost",
130
+ size: "sm",
131
+ "aria-label": label,
132
+ disabled: !enabled,
133
+ onMouseDown: (event) => event.preventDefault(),
134
+ children: [/* @__PURE__ */ jsx(Icon, { name: icon }), /* @__PURE__ */ jsx(Icon, {
135
+ name: "i-lucide-chevron-down",
136
+ className: "size-3"
137
+ })]
138
+ })
139
+ })
140
+ }), /* @__PURE__ */ jsx(TooltipContent, { children: label })] }), /* @__PURE__ */ jsx(DropdownMenuContent, {
141
+ align: "start",
142
+ children: menu().map((entry, index) => /* @__PURE__ */ jsx(MenuRow, { entry }, index))
143
+ })] });
144
+ }
87
145
  function ToolbarButton({ item, editor, state }) {
88
146
  const { active, enabled } = state;
89
147
  const button = /* @__PURE__ */ jsx(Button, {
@@ -1 +1 @@
1
- {"version":3,"file":"EditorToolbar.js","names":[],"sources":["../../src/react/EditorToolbar.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useEditorState } from '@tiptap/react'\nimport { type ComponentType, type ReactNode, useState } from 'react'\nimport { type CommandDef, type CommandState, commandState, type MenuItem, type PlacedToolbarGroup, toolbarOf } from '../core/commands'\nimport { borrowedFeatureContext, editorEntryOf } from '../core/editor-ui'\nimport type { FeatureContext } from '../core/feature'\nimport { useEditorContext } from './context'\nimport type { ReactPreset } from './EditorSurface'\nimport { Icon } from './Icon'\nimport { Button } from './ui/button'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from './ui/dropdown-menu'\nimport { Separator } from './ui/separator'\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip'\nimport { shortcutLabel } from './key-labels'\nimport { useEditorI18n } from './use-editor-i18n'\nimport { useStore } from './use-store'\nimport { cn } from './ui/utils'\n\ntype ItemComponent = ComponentType<{ editor: Editor }>\ntype Item = CommandDef<ItemComponent>\ntype Group = PlacedToolbarGroup<ItemComponent>\n\nconst NO_EDITOR: CommandState = { visible: true, enabled: false, active: false }\n\n// Factories are plain functions here (React features must not call hooks in them): built once per preset, app-wide,\n// keyed by the toolbar's `ctx` so each toolbar's items delegate to its own editor.\nconst built = new WeakMap<FeatureContext, WeakMap<ReactPreset, Group[]>>()\nfunction groupsOf(ctx: FeatureContext, preset: ReactPreset | undefined): Group[] {\n if (!preset) return []\n let byPreset = built.get(ctx)\n if (!byPreset) built.set(ctx, byPreset = new WeakMap())\n const cached = byPreset.get(preset)\n if (cached) return cached\n const groups = toolbarOf(preset, ctx)\n byPreset.set(preset, groups)\n return groups\n}\n\n/**\n * The toolbar of an editor, placed anywhere on the page. `editor` is the instance it works on (from the surface's\n * `onReady`); the preset registered for that instance (`editorEntryOf`) supplies the groups. Without an editor (not\n * mounted yet, or read-only by the host's choice) it renders no groups. A destroyed editor counts as none: a host still\n * holds the old instance for the render in which its keyed surface remounts. Never reads a global active editor.\n */\nexport function EditorToolbar({ editor: given }: { editor?: Editor }) {\n const editor = given?.isDestroyed ? undefined : given\n const app = useEditorContext()\n const { t } = useEditorI18n()\n\n // The context toolbar factories see: settings and fetch of the app, everything per-surface delegates to the editor's\n // registered context while there is one. Built once; `ref` keeps it pointing at the current editor.\n const [state] = useState(() => {\n const ref: { current: Editor | undefined } = { current: editor }\n return { ref, ctx: borrowedFeatureContext({ app, getEditor: () => ref.current }) }\n })\n state.ref.current = editor\n\n // `active`, `can()` and the abilities read the editor state: re-render on every transaction.\n useEditorState({\n editor: editor ?? null,\n selector: ({ editor: current }) => current && !current.isDestroyed ? current.state : null,\n equalityFn: () => false,\n })\n // `active`/`visible`/`menu` may read the settings: re-render when they change.\n useStore(app.settings)\n\n // Every enabled/visible/active state comes from `commandState`.\n const groups = groupsOf(state.ctx, editorEntryOf(editor)?.preset as ReactPreset | undefined)\n .filter(group => !editor || !group.visible || group.visible(editor))\n .map(group => ({\n group,\n items: group.items\n .map(item => ({ item, state: editor ? commandState(editor, item, 'selection') : NO_EDITOR }))\n .filter(({ state: itemState }) => itemState.visible),\n }))\n .filter(({ items }) => items.length > 0)\n\n return (\n <TooltipProvider>\n <div className=\"flex flex-wrap items-center gap-1.5\" role=\"toolbar\" aria-label={t('toolbar.label')}>\n {groups.map(({ group, items }, index) => (\n <div key={group.id} className=\"contents\">\n {index > 0 && <Separator orientation=\"vertical\" className=\"mx-2 h-6 data-[orientation=vertical]:h-6\" />}\n <div className=\"flex items-center gap-0.5\">\n {items.map(({ item, state }) => 'component' in item\n ? <item.component key={item.id} editor={editor!} />\n : <ToolbarButton\n key={item.id}\n item={item}\n editor={editor}\n state={state}\n />)}\n </div>\n </div>\n ))}\n </div>\n </TooltipProvider>\n )\n}\n\nfunction ToolbarButton({ item, editor, state }: { item: Item, editor: Editor | undefined, state: CommandState }) {\n const { active, enabled } = state\n const button = (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={item.label}\n aria-pressed={item.active ? active : undefined}\n data-state={item.active ? (active ? 'on' : 'off') : undefined}\n disabled={!enabled}\n className={cn(active && 'bg-primary/10 text-primary hover:bg-primary/15 hover:text-primary')}\n onMouseDown={event => event.preventDefault()}\n onClick={'run' in item && editor ? () => item.run(editor) : undefined}\n >\n <Icon name={item.icon} />\n </Button>\n )\n const tooltip = (trigger: ReactNode) => (\n <Tooltip>\n <TooltipTrigger asChild>{trigger}</TooltipTrigger>\n <TooltipContent>\n {item.label}\n {item.kbds && item.kbds.length > 0 && (\n <kbd data-slot=\"kbd\" className=\"rounded-sm bg-background/20 px-1 font-sans\">{shortcutLabel(item.kbds)}</kbd>\n )}\n </TooltipContent>\n </Tooltip>\n )\n if (!('menu' in item)) return tooltip(button)\n return (\n <DropdownMenu>\n {tooltip(<DropdownMenuTrigger asChild>{button}</DropdownMenuTrigger>)}\n <DropdownMenuContent align=\"start\">\n {editor && item.menu(editor).map((entry, index) => <MenuRow key={index} entry={entry} />)}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction MenuRow({ entry }: { entry: MenuItem }) {\n if (entry.type === 'label') return <DropdownMenuLabel>{entry.label}</DropdownMenuLabel>\n if (entry.type === 'separator') return <DropdownMenuSeparator />\n return (\n <DropdownMenuItem disabled={entry.disabled} onSelect={() => entry.onSelect?.()}>\n {entry.icon && <Icon name={entry.icon} />}\n <span>{entry.label}</span>\n {entry.description && <span className=\"ml-auto text-xs text-muted-foreground\">{entry.description}</span>}\n </DropdownMenuItem>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EditorToolbar.js","names":[],"sources":["../../src/react/EditorToolbar.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useEditorState } from '@tiptap/react'\nimport { type ComponentType, type ReactNode, useLayoutEffect, useRef, useState } from 'react'\nimport { type CommandDef, type CommandState, commandState, type MenuItem, type PlacedToolbarGroup, toolbarOf } from '../core/commands'\nimport { collapsedGroupMenu, fitToolbar, type ToolbarFit } from '../core/toolbar-fit'\nimport { borrowedFeatureContext, editorEntryOf } from '../core/editor-ui'\nimport type { FeatureContext } from '../core/feature'\nimport { useEditorContext } from './context'\nimport type { ReactPreset } from './EditorSurface'\nimport { Icon } from './Icon'\nimport { Button } from './ui/button'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from './ui/dropdown-menu'\nimport { Separator } from './ui/separator'\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip'\nimport { shortcutLabel } from './key-labels'\nimport { useEditorI18n } from './use-editor-i18n'\nimport { useStore } from './use-store'\nimport { cn } from './ui/utils'\n\ntype ItemComponent = ComponentType<{ editor: Editor }>\ntype Item = CommandDef<ItemComponent>\ntype Group = PlacedToolbarGroup<ItemComponent>\n\nconst NO_EDITOR: CommandState = { visible: true, enabled: false, active: false }\n\n// Factories are plain functions here (React features must not call hooks in them): built once per preset, app-wide,\n// keyed by the toolbar's `ctx` so each toolbar's items delegate to its own editor.\nconst built = new WeakMap<FeatureContext, WeakMap<ReactPreset, Group[]>>()\nfunction groupsOf(ctx: FeatureContext, preset: ReactPreset | undefined): Group[] {\n if (!preset) return []\n let byPreset = built.get(ctx)\n if (!byPreset) built.set(ctx, byPreset = new WeakMap())\n const cached = byPreset.get(preset)\n if (cached) return cached\n const groups = toolbarOf(preset, ctx)\n byPreset.set(preset, groups)\n return groups\n}\n\n/**\n * The toolbar of an editor, placed anywhere on the page. `editor` is the instance it works on (from the surface's\n * `onReady`); the preset registered for that instance (`editorEntryOf`) supplies the groups. Without an editor (not\n * mounted yet, or read-only by the host's choice) it renders no groups. A destroyed editor counts as none: a host still\n * holds the old instance for the render in which its keyed surface remounts. Never reads a global active editor.\n * `overflow`: `wrap` (default) breaks onto further lines; `collapse` keeps one line, and the groups that do not fit\n * collapse from the end into one dropdown each (`component` commands stay beside it). The row then takes the width its\n * container gives it.\n */\nexport function EditorToolbar({ editor: given, overflow = 'wrap' }: { editor?: Editor, overflow?: 'wrap' | 'collapse' }) {\n const editor = given?.isDestroyed ? undefined : given\n const app = useEditorContext()\n const { t } = useEditorI18n()\n\n // The context toolbar factories see: settings and fetch of the app, everything per-surface delegates to the editor's\n // registered context while there is one. Built once; `ref` keeps it pointing at the current editor.\n const [state] = useState(() => {\n const ref: { current: Editor | undefined } = { current: editor }\n return { ref, ctx: borrowedFeatureContext({ app, getEditor: () => ref.current }) }\n })\n state.ref.current = editor\n\n // `active`, `can()` and the abilities read the editor state: re-render on every transaction.\n useEditorState({\n editor: editor ?? null,\n selector: ({ editor: current }) => current && !current.isDestroyed ? current.state : null,\n equalityFn: () => false,\n })\n // `active`/`visible`/`menu` may read the settings: re-render when they change.\n useStore(app.settings)\n\n // Every enabled/visible/active state comes from `commandState`.\n const groups = groupsOf(state.ctx, editorEntryOf(editor)?.preset as ReactPreset | undefined)\n .filter(group => !editor || !group.visible || group.visible(editor))\n .map(group => ({\n group,\n items: group.items\n .map(item => ({ item, state: editor ? commandState(editor, item, 'selection') : NO_EDITOR }))\n .filter(({ state: itemState }) => itemState.visible),\n }))\n .filter(({ items }) => items.length > 0)\n\n // `collapse`: how many leading groups are expanded (all until the first measurement).\n const row = useRef<HTMLDivElement>(null)\n const fit = useRef<ToolbarFit | undefined>(undefined)\n const [expandedCount, setExpandedCount] = useState(Number.POSITIVE_INFINITY)\n useLayoutEffect(() => {\n if (overflow !== 'collapse' || !row.current) return\n const current = fitToolbar(row.current, setExpandedCount)\n fit.current = current\n return () => {\n current.dispose()\n fit.current = undefined\n setExpandedCount(Number.POSITIVE_INFINITY)\n }\n }, [overflow])\n useLayoutEffect(() => fit.current?.refresh())\n const collapse = overflow === 'collapse'\n\n return (\n <TooltipProvider>\n <div\n ref={row}\n className={cn('flex items-center gap-1.5', collapse ? 'min-w-0 flex-nowrap overflow-hidden' : 'flex-wrap')}\n role=\"toolbar\"\n aria-label={t('toolbar.label')}\n >\n {groups.map(({ group, items }, index) => {\n const runnable = items.filter(({ item }) => !('component' in item))\n const collapsed = collapse && index >= expandedCount && runnable.length > 0\n return (\n <div\n key={group.id}\n className={collapse ? 'flex shrink-0 items-center gap-1.5' : 'contents'}\n data-toolbar-group={group.id}\n data-items={runnable.length}\n data-collapsed={collapsed ? '' : undefined}\n >\n {index > 0 && <Separator orientation=\"vertical\" className=\"mx-2 h-6 data-[orientation=vertical]:h-6\" />}\n <div className=\"flex items-center gap-0.5\">\n {collapsed\n ? (\n <>\n <CollapsedGroup\n label={group.labelKey ? t(group.labelKey) : group.id}\n icon={runnable[0]!.item.icon}\n enabled={runnable.some(({ state: itemState }) => itemState.enabled)}\n menu={() => collapsedGroupMenu(items.map(({ item, state: itemState }) => ({ cmd: item, state: itemState })), editor)}\n />\n {items.map(({ item }) => 'component' in item && <item.component key={item.id} editor={editor!} />)}\n </>\n )\n : items.map(({ item, state }) => 'component' in item\n ? <item.component key={item.id} editor={editor!} />\n : <ToolbarButton\n key={item.id}\n item={item}\n editor={editor}\n state={state}\n />)}\n </div>\n </div>\n )\n })}\n </div>\n </TooltipProvider>\n )\n}\n\n/** A group collapsed to one dropdown: the first command's icon, a chevron, the group's name as tooltip. */\nfunction CollapsedGroup({ label, icon, enabled, menu }: { label: string, icon: string, enabled: boolean, menu: () => MenuItem[] }) {\n return (\n <DropdownMenu>\n <Tooltip>\n <TooltipTrigger asChild>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"sm\" aria-label={label} disabled={!enabled} onMouseDown={event => event.preventDefault()}>\n <Icon name={icon} />\n <Icon name=\"i-lucide-chevron-down\" className=\"size-3\" />\n </Button>\n </DropdownMenuTrigger>\n </TooltipTrigger>\n <TooltipContent>{label}</TooltipContent>\n </Tooltip>\n <DropdownMenuContent align=\"start\">\n {menu().map((entry, index) => <MenuRow key={index} entry={entry} />)}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction ToolbarButton({ item, editor, state }: { item: Item, editor: Editor | undefined, state: CommandState }) {\n const { active, enabled } = state\n const button = (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={item.label}\n aria-pressed={item.active ? active : undefined}\n data-state={item.active ? (active ? 'on' : 'off') : undefined}\n disabled={!enabled}\n className={cn(active && 'bg-primary/10 text-primary hover:bg-primary/15 hover:text-primary')}\n onMouseDown={event => event.preventDefault()}\n onClick={'run' in item && editor ? () => item.run(editor) : undefined}\n >\n <Icon name={item.icon} />\n </Button>\n )\n const tooltip = (trigger: ReactNode) => (\n <Tooltip>\n <TooltipTrigger asChild>{trigger}</TooltipTrigger>\n <TooltipContent>\n {item.label}\n {item.kbds && item.kbds.length > 0 && (\n <kbd data-slot=\"kbd\" className=\"rounded-sm bg-background/20 px-1 font-sans\">{shortcutLabel(item.kbds)}</kbd>\n )}\n </TooltipContent>\n </Tooltip>\n )\n if (!('menu' in item)) return tooltip(button)\n return (\n <DropdownMenu>\n {tooltip(<DropdownMenuTrigger asChild>{button}</DropdownMenuTrigger>)}\n <DropdownMenuContent align=\"start\">\n {editor && item.menu(editor).map((entry, index) => <MenuRow key={index} entry={entry} />)}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction MenuRow({ entry }: { entry: MenuItem }) {\n if (entry.type === 'label') return <DropdownMenuLabel>{entry.label}</DropdownMenuLabel>\n if (entry.type === 'separator') return <DropdownMenuSeparator />\n return (\n <DropdownMenuItem disabled={entry.disabled} onSelect={() => entry.onSelect?.()}>\n {entry.icon && <Icon name={entry.icon} />}\n <span>{entry.label}</span>\n {entry.description && <span className=\"ml-auto text-xs text-muted-foreground\">{entry.description}</span>}\n </DropdownMenuItem>\n )\n}\n"],"mappings":"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"}
@@ -44,7 +44,11 @@ function blockLinesOfContent(content) {
44
44
  below: (id) => below.get(id) ?? NOTHING
45
45
  };
46
46
  }
47
- const textOf = (node) => node.type === "text" ? node.text ?? "" : (node.content ?? []).map(textOf).join("");
47
+ const textOf = (node) => {
48
+ if (node.type === "text") return node.text ?? "";
49
+ const children = node.content ?? [];
50
+ return children.map(textOf).join(children.some(isInline) ? "" : " ");
51
+ };
48
52
  /** The type and text of Block `blockId` of `content` (what `blockSnippet` shows); `null` when there is no such Block. */
49
53
  function blockExcerptOf(content, blockId) {
50
54
  const node = findBlock(content, blockId);
@@ -1 +1 @@
1
- {"version":3,"file":"document-blocks.js","names":[],"sources":["../../src/server/document-blocks.ts"],"sourcesContent":["import type { JSONContent } from '@tiptap/core'\nimport type { BlockLines } from '../features/ai/protocol'\nimport type { BlockExcerpt } from '../core/block-snippet'\n\n// Reading the Blocks of a Document's stored JSON without an editor: which block IDs exist, which lie inside which, and the\n// text of one. The same rule as `projectDocument` (`core/block-index`): every node with an `id` attribute is a Block, except the\n// inline nodes of a textblock (a mention's `id` is a user's).\n\n/** Inline nodes: an `id` on one is not a Block's. */\nexport const INLINE_NODES: ReadonlySet<string> = new Set(['text', 'hardBreak', 'mention', 'tag', 'reference'])\n\nconst isInline = (node: JSONContent): boolean => node.type !== undefined && INLINE_NODES.has(node.type)\n\n/** The node of Block `blockId` in `content`, `null` when no node carries it. */\nexport function findBlock(content: JSONContent, blockId: string): JSONContent | null {\n for (const child of content.content ?? []) {\n if (isInline(child)) continue\n if (child.attrs?.id === blockId) return child\n const nested = findBlock(child, blockId)\n if (nested) return nested\n }\n return null\n}\n\nconst NOTHING: ReadonlySet<string> = new Set()\n\n/** `BlockLines` of `content`: what `checkAgentOps` reads of the text the chat model sees, taken from the Document's JSON (the block IDs and which lie inside which). */\nexport function blockLinesOfContent(content: JSONContent): BlockLines {\n const ids = new Set<string>()\n const below = new Map<string, Set<string>>()\n const walk = (node: JSONContent, enclosing: readonly Set<string>[]): void => {\n for (const child of node.content ?? []) {\n if (isInline(child)) continue\n const id = child.attrs?.id\n let inside = enclosing\n if (typeof id === 'string' && id) {\n ids.add(id)\n for (const set of enclosing) set.add(id)\n const own = below.get(id) ?? new Set<string>()\n below.set(id, own)\n inside = [...enclosing, own]\n }\n walk(child, inside)\n }\n }\n walk(content, [])\n return { ids, below: id => below.get(id) ?? NOTHING }\n}\n\nconst textOf = (node: JSONContent): string => node.type === 'text' ? node.text ?? '' : (node.content ?? []).map(textOf).join('')\n\n/** The type and text of Block `blockId` of `content` (what `blockSnippet` shows); `null` when there is no such Block. */\nexport function blockExcerptOf(content: JSONContent, blockId: string): BlockExcerpt | null {\n const node = findBlock(content, blockId)\n return node && { type: node.type ?? '', text: textOf(node) }\n}\n"],"mappings":";;AASA,MAAa,+BAAoC,IAAI,IAAI;CAAC;CAAQ;CAAa;CAAW;CAAO;AAAW,CAAC;AAE7G,MAAM,YAAY,SAA+B,KAAK,SAAS,KAAA,KAAa,aAAa,IAAI,KAAK,IAAI;;AAGtG,SAAgB,UAAU,SAAsB,SAAqC;CACnF,KAAK,MAAM,SAAS,QAAQ,WAAW,CAAC,GAAG;EACzC,IAAI,SAAS,KAAK,GAAG;EACrB,IAAI,MAAM,OAAO,OAAO,SAAS,OAAO;EACxC,MAAM,SAAS,UAAU,OAAO,OAAO;EACvC,IAAI,QAAQ,OAAO;CACrB;CACA,OAAO;AACT;AAEA,MAAM,0BAA+B,IAAI,IAAI;;AAG7C,SAAgB,oBAAoB,SAAkC;CACpE,MAAM,sBAAM,IAAI,IAAY;CAC5B,MAAM,wBAAQ,IAAI,IAAyB;CAC3C,MAAM,QAAQ,MAAmB,cAA4C;EAC3E,KAAK,MAAM,SAAS,KAAK,WAAW,CAAC,GAAG;GACtC,IAAI,SAAS,KAAK,GAAG;GACrB,MAAM,KAAK,MAAM,OAAO;GACxB,IAAI,SAAS;GACb,IAAI,OAAO,OAAO,YAAY,IAAI;IAChC,IAAI,IAAI,EAAE;IACV,KAAK,MAAM,OAAO,WAAW,IAAI,IAAI,EAAE;IACvC,MAAM,MAAM,MAAM,IAAI,EAAE,qBAAK,IAAI,IAAY;IAC7C,MAAM,IAAI,IAAI,GAAG;IACjB,SAAS,CAAC,GAAG,WAAW,GAAG;GAC7B;GACA,KAAK,OAAO,MAAM;EACpB;CACF;CACA,KAAK,SAAS,CAAC,CAAC;CAChB,OAAO;EAAE;EAAK,QAAO,OAAM,MAAM,IAAI,EAAE,KAAK;CAAQ;AACtD;AAEA,MAAM,UAAU,SAA8B,KAAK,SAAS,SAAS,KAAK,QAAQ,MAAM,KAAK,WAAW,CAAC,EAAA,CAAG,IAAI,MAAM,CAAC,CAAC,KAAK,EAAE;;AAG/H,SAAgB,eAAe,SAAsB,SAAsC;CACzF,MAAM,OAAO,UAAU,SAAS,OAAO;CACvC,OAAO,QAAQ;EAAE,MAAM,KAAK,QAAQ;EAAI,MAAM,OAAO,IAAI;CAAE;AAC7D"}
1
+ {"version":3,"file":"document-blocks.js","names":[],"sources":["../../src/server/document-blocks.ts"],"sourcesContent":["import type { JSONContent } from '@tiptap/core'\nimport type { BlockLines } from '../features/ai/protocol'\nimport type { BlockExcerpt } from '../core/block-snippet'\n\n// Reading the Blocks of a Document's stored JSON without an editor: which block IDs exist, which lie inside which, and the\n// text of one. The same rule as `projectDocument` (`core/block-index`): every node with an `id` attribute is a Block, except the\n// inline nodes of a textblock (a mention's `id` is a user's).\n\n/** Inline nodes: an `id` on one is not a Block's. */\nexport const INLINE_NODES: ReadonlySet<string> = new Set(['text', 'hardBreak', 'mention', 'tag', 'reference'])\n\nconst isInline = (node: JSONContent): boolean => node.type !== undefined && INLINE_NODES.has(node.type)\n\n/** The node of Block `blockId` in `content`, `null` when no node carries it. */\nexport function findBlock(content: JSONContent, blockId: string): JSONContent | null {\n for (const child of content.content ?? []) {\n if (isInline(child)) continue\n if (child.attrs?.id === blockId) return child\n const nested = findBlock(child, blockId)\n if (nested) return nested\n }\n return null\n}\n\nconst NOTHING: ReadonlySet<string> = new Set()\n\n/** `BlockLines` of `content`: what `checkAgentOps` reads of the text the chat model sees, taken from the Document's JSON (the block IDs and which lie inside which). */\nexport function blockLinesOfContent(content: JSONContent): BlockLines {\n const ids = new Set<string>()\n const below = new Map<string, Set<string>>()\n const walk = (node: JSONContent, enclosing: readonly Set<string>[]): void => {\n for (const child of node.content ?? []) {\n if (isInline(child)) continue\n const id = child.attrs?.id\n let inside = enclosing\n if (typeof id === 'string' && id) {\n ids.add(id)\n for (const set of enclosing) set.add(id)\n const own = below.get(id) ?? new Set<string>()\n below.set(id, own)\n inside = [...enclosing, own]\n }\n walk(child, inside)\n }\n }\n walk(content, [])\n return { ids, below: id => below.get(id) ?? NOTHING }\n}\n\n// Text runs of a textblock join directly; nested blocks (a table's cells, a list's items) are separated by a space, as `agentOpSnippet` reads them.\nconst textOf = (node: JSONContent): string => {\n if (node.type === 'text') return node.text ?? ''\n const children = node.content ?? []\n return children.map(textOf).join(children.some(isInline) ? '' : ' ')\n}\n\n/** The type and text of Block `blockId` of `content` (what `blockSnippet` shows); `null` when there is no such Block. */\nexport function blockExcerptOf(content: JSONContent, blockId: string): BlockExcerpt | null {\n const node = findBlock(content, blockId)\n return node && { type: node.type ?? '', text: textOf(node) }\n}\n"],"mappings":";;AASA,MAAa,+BAAoC,IAAI,IAAI;CAAC;CAAQ;CAAa;CAAW;CAAO;AAAW,CAAC;AAE7G,MAAM,YAAY,SAA+B,KAAK,SAAS,KAAA,KAAa,aAAa,IAAI,KAAK,IAAI;;AAGtG,SAAgB,UAAU,SAAsB,SAAqC;CACnF,KAAK,MAAM,SAAS,QAAQ,WAAW,CAAC,GAAG;EACzC,IAAI,SAAS,KAAK,GAAG;EACrB,IAAI,MAAM,OAAO,OAAO,SAAS,OAAO;EACxC,MAAM,SAAS,UAAU,OAAO,OAAO;EACvC,IAAI,QAAQ,OAAO;CACrB;CACA,OAAO;AACT;AAEA,MAAM,0BAA+B,IAAI,IAAI;;AAG7C,SAAgB,oBAAoB,SAAkC;CACpE,MAAM,sBAAM,IAAI,IAAY;CAC5B,MAAM,wBAAQ,IAAI,IAAyB;CAC3C,MAAM,QAAQ,MAAmB,cAA4C;EAC3E,KAAK,MAAM,SAAS,KAAK,WAAW,CAAC,GAAG;GACtC,IAAI,SAAS,KAAK,GAAG;GACrB,MAAM,KAAK,MAAM,OAAO;GACxB,IAAI,SAAS;GACb,IAAI,OAAO,OAAO,YAAY,IAAI;IAChC,IAAI,IAAI,EAAE;IACV,KAAK,MAAM,OAAO,WAAW,IAAI,IAAI,EAAE;IACvC,MAAM,MAAM,MAAM,IAAI,EAAE,qBAAK,IAAI,IAAY;IAC7C,MAAM,IAAI,IAAI,GAAG;IACjB,SAAS,CAAC,GAAG,WAAW,GAAG;GAC7B;GACA,KAAK,OAAO,MAAM;EACpB;CACF;CACA,KAAK,SAAS,CAAC,CAAC;CAChB,OAAO;EAAE;EAAK,QAAO,OAAM,MAAM,IAAI,EAAE,KAAK;CAAQ;AACtD;AAGA,MAAM,UAAU,SAA8B;CAC5C,IAAI,KAAK,SAAS,QAAQ,OAAO,KAAK,QAAQ;CAC9C,MAAM,WAAW,KAAK,WAAW,CAAC;CAClC,OAAO,SAAS,IAAI,MAAM,CAAC,CAAC,KAAK,SAAS,KAAK,QAAQ,IAAI,KAAK,GAAG;AACrE;;AAGA,SAAgB,eAAe,SAAsB,SAAsC;CACzF,MAAM,OAAO,UAAU,SAAS,OAAO;CACvC,OAAO,QAAQ;EAAE,MAAM,KAAK,QAAQ;EAAI,MAAM,OAAO,IAAI;CAAE;AAC7D"}
@@ -386,24 +386,26 @@
386
386
 
387
387
  /* ---- 2. Token bridge-------------------------------------------------------------------------------- */
388
388
  .obsidian-theme {
389
- /* The editor's own tokens (kit independent: shadcn hosts read these, too). */
389
+ /* The editor's own tokens (kit independent: shadcn hosts read these, too). A theme may leave an Obsidian variable
390
+ unset (prism sets its accent only under its Style Settings classes, cupertino has no hover modifier), which makes
391
+ every token built on it empty: those tokens fall back to Obsidian's own default, and the `--ui-*` twins read them. */
390
392
  --sk-bg: var(--background-primary);
391
393
  --sk-fg: var(--text-normal);
392
394
  --sk-muted: var(--text-muted);
393
395
  --sk-border: var(--background-modifier-border);
394
- --sk-primary: var(--interactive-accent);
395
- --sk-primary-fg: var(--text-on-accent);
396
+ --sk-primary: var(--interactive-accent, hsl(var(--accent-h), var(--accent-s), var(--accent-l)));
397
+ --sk-primary-fg: var(--text-on-accent, white);
396
398
  --sk-success: var(--color-green);
397
399
  --sk-warning: var(--color-orange);
398
400
  --sk-danger: var(--color-red);
399
401
  --sk-info: var(--color-blue);
400
402
  --sk-radius: var(--radius-s);
401
403
  --sk-bg-elevated: var(--background-secondary);
402
- --sk-bg-accented: var(--background-modifier-hover);
404
+ --sk-bg-accented: var(--background-modifier-hover, color-mix(in srgb, var(--text-normal) 7.5%, transparent));
403
405
  --sk-fg-highlighted: var(--text-normal);
404
406
  --sk-fg-toned: var(--text-muted);
405
407
  --sk-fg-dimmed: var(--text-faint);
406
- --sk-border-accented: var(--background-modifier-border-hover);
408
+ --sk-border-accented: var(--background-modifier-border-hover, var(--background-modifier-border));
407
409
  /* Nuxt UI's own components. */
408
410
  --ui-text: var(--text-normal);
409
411
  --ui-text-highlighted: var(--text-normal);
@@ -413,14 +415,14 @@
413
415
  --ui-bg: var(--background-primary);
414
416
  --ui-bg-elevated: var(--background-secondary);
415
417
  --ui-border: var(--background-modifier-border);
416
- --ui-border-accented: var(--background-modifier-border-hover);
417
- --ui-primary: var(--interactive-accent);
418
+ --ui-border-accented: var(--sk-border-accented);
419
+ --ui-primary: var(--sk-primary);
418
420
  --ui-info: var(--color-blue);
419
421
  --ui-success: var(--color-success, var(--color-green));
420
422
  --ui-warning: var(--color-orange);
421
423
  --ui-error: var(--color-red);
422
424
  --ui-bg-muted: var(--background-secondary);
423
- --ui-bg-accented: var(--background-modifier-hover);
425
+ --ui-bg-accented: var(--sk-bg-accented);
424
426
  --ui-bg-inverted: var(--text-normal);
425
427
  --ui-text-inverted: var(--background-primary);
426
428
  --ui-border-muted: var(--background-modifier-border);
@@ -5,10 +5,10 @@ import { basketById } from "../features/composites/baskets.js";
5
5
  import { useUiKit } from "../vue/kit/use-ui-kit.js";
6
6
  import { useEditorSettings } from "../vue/settings.js";
7
7
  import { useEditorI18n } from "../vue/use-editor-i18n.js";
8
+ import _sfc_main$1 from "../features/composites/components/DocumentBadges.js";
8
9
  import { DRAG_MIME, focusCrumbs, indexItems, isOutlineHeader, itemBasket } from "./tree-items.js";
9
10
  import { refocusRow, rowPosition } from "./row-focus.js";
10
11
  import { rowOf } from "./tree-model.js";
11
- import _sfc_main$1 from "../features/composites/components/DocumentBadges.js";
12
12
  import { useTreeModel } from "./use-tree-model.js";
13
13
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, mergeModels, normalizeClass, normalizeStyle, openBlock, renderList, renderSlot, toDisplayString, unref, useModel, withCtx, withKeys, withModifiers } from "vue";
14
14
  //#region src/tree/DocumentTree.vue
@@ -3,10 +3,10 @@ import { useEditorSettings } from "../vue/settings.js";
3
3
  import { useEditorContext } from "../vue/context.js";
4
4
  import { useEditorI18n } from "../vue/use-editor-i18n.js";
5
5
  import { useStoreRef } from "../vue/store-ref.js";
6
+ import { useFileImport } from "../features/media/vue.js";
6
7
  import { treeView } from "./tree-view-model.js";
7
8
  import { createTreeModel } from "./tree-model.js";
8
9
  import { createTreeExecutor, treeEditorOf } from "./tree-host.js";
9
- import { useFileImport } from "../features/media/vue.js";
10
10
  import { computed, onScopeDispose, shallowRef, watch } from "vue";
11
11
  //#region src/tree/use-tree-model.ts
12
12
  /**
@@ -82,18 +82,18 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
82
82
  editor: import("vue").ComputedRef<Editor | undefined>;
83
83
  preset: ComposedEditor<Component>;
84
84
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
85
- "update:modelValue": (content: JSONContent) => any;
86
- ready: (editor: Editor) => any;
87
85
  "update:sidebarOpen": (value: boolean) => any;
88
86
  "update:commentsOpen": (value: boolean) => any;
87
+ ready: (editor: Editor) => any;
88
+ "update:modelValue": (content: JSONContent) => any;
89
89
  metadataCreate: (list: string, item: MetadataItem) => any;
90
90
  contentError: (error: Error) => any;
91
91
  settingsOpen: (tab?: string | undefined) => any;
92
92
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
93
- "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
94
- onReady?: ((editor: Editor) => any) | undefined;
95
93
  "onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
96
94
  "onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
95
+ onReady?: ((editor: Editor) => any) | undefined;
96
+ "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
97
97
  onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
98
98
  onContentError?: ((error: Error) => any) | undefined;
99
99
  onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
@@ -41,18 +41,18 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
41
41
  editor: import("vue").ComputedRef<Editor | undefined>;
42
42
  preset: EditorPreset<Component>;
43
43
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
44
- "update:modelValue": (content: JSONContent) => any;
45
- ready: (editor: Editor) => any;
46
44
  "update:sidebarOpen": (value: boolean) => any;
47
45
  "update:commentsOpen": (value: boolean) => any;
46
+ ready: (editor: Editor) => any;
47
+ "update:modelValue": (content: JSONContent) => any;
48
48
  metadataCreate: (list: string, item: MetadataItem) => any;
49
49
  contentError: (error: Error) => any;
50
50
  settingsOpen: (tab?: string | undefined) => any;
51
51
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
52
- "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
53
- onReady?: ((editor: Editor) => any) | undefined;
54
52
  "onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
55
53
  "onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
54
+ onReady?: ((editor: Editor) => any) | undefined;
55
+ "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
56
56
  onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
57
57
  onContentError?: ((error: Error) => any) | undefined;
58
58
  onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
@@ -43,18 +43,18 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
43
43
  settingsOpen: import("vue").Ref<boolean, boolean>;
44
44
  settingsTab: import("vue").Ref<string | undefined, string | undefined>;
45
45
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
46
- "update:modelValue": (content: JSONContent) => any;
47
- ready: (editor: Editor) => any;
48
46
  "update:sidebarOpen": (value: boolean) => any;
49
47
  "update:commentsOpen": (value: boolean) => any;
48
+ ready: (editor: Editor) => any;
49
+ "update:modelValue": (content: JSONContent) => any;
50
50
  metadataCreate: (list: string, item: MetadataItem) => any;
51
51
  contentError: (error: Error) => any;
52
52
  settingsOpen: (tab?: string | undefined) => any;
53
53
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
54
- "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
55
- onReady?: ((editor: Editor) => any) | undefined;
56
54
  "onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
57
55
  "onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
56
+ onReady?: ((editor: Editor) => any) | undefined;
57
+ "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
58
58
  onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
59
59
  onContentError?: ((error: Error) => any) | undefined;
60
60
  onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;