@abraca/nuxt 2.13.0 → 2.15.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 (45) hide show
  1. package/dist/module.d.mts +15 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +2 -0
  4. package/dist/runtime/assets/editor.css +3 -1
  5. package/dist/runtime/components/ACodeEditor.vue +138 -23
  6. package/dist/runtime/components/ADocViewToggle.d.vue.ts +40 -0
  7. package/dist/runtime/components/ADocViewToggle.vue +234 -0
  8. package/dist/runtime/components/ADocViewToggle.vue.d.ts +40 -0
  9. package/dist/runtime/components/ADocumentTree.vue +1 -1
  10. package/dist/runtime/components/AEditor.vue +183 -15
  11. package/dist/runtime/components/ANodePanel.vue +91 -91
  12. package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
  13. package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
  14. package/dist/runtime/components/aware/ASlider.d.vue.ts +1 -1
  15. package/dist/runtime/components/aware/ASlider.vue.d.ts +1 -1
  16. package/dist/runtime/components/docs/ADocsSearchButton.d.vue.ts +1 -1
  17. package/dist/runtime/components/docs/ADocsSearchButton.vue.d.ts +1 -1
  18. package/dist/runtime/components/editor/AColorPalettePopover.vue +97 -5
  19. package/dist/runtime/components/editor/ADocSuggestMenu.d.vue.ts +7 -0
  20. package/dist/runtime/components/editor/ADocSuggestMenu.vue +68 -0
  21. package/dist/runtime/components/editor/ADocSuggestMenu.vue.d.ts +7 -0
  22. package/dist/runtime/components/editor/AIconPickerPopover.vue +81 -3
  23. package/dist/runtime/components/editor/AMetaNumberStepper.d.vue.ts +40 -0
  24. package/dist/runtime/components/editor/AMetaNumberStepper.vue +214 -0
  25. package/dist/runtime/components/editor/AMetaNumberStepper.vue.d.ts +40 -0
  26. package/dist/runtime/components/renderers/ACalendarRenderer.vue +7 -1
  27. package/dist/runtime/components/renderers/calendar/ACalendarToolbar.d.vue.ts +2 -2
  28. package/dist/runtime/components/renderers/calendar/ACalendarToolbar.vue.d.ts +2 -2
  29. package/dist/runtime/components/renderers/media/MediaTransportBar.d.vue.ts +2 -2
  30. package/dist/runtime/components/renderers/media/MediaTransportBar.vue.d.ts +2 -2
  31. package/dist/runtime/composables/useDocLinkPick.d.ts +9 -8
  32. package/dist/runtime/composables/useDocLinkPick.js +7 -18
  33. package/dist/runtime/composables/useDocSuggest.d.ts +34 -0
  34. package/dist/runtime/composables/useDocSuggest.js +56 -0
  35. package/dist/runtime/composables/useTouchDrag.d.ts +21 -4
  36. package/dist/runtime/composables/useTouchDrag.js +30 -0
  37. package/dist/runtime/extensions/doc-link-drop.js +2 -2
  38. package/dist/runtime/extensions/doc-suggest.d.ts +28 -0
  39. package/dist/runtime/extensions/doc-suggest.js +85 -0
  40. package/dist/runtime/extensions/views/MetaFieldView.vue +17 -28
  41. package/dist/runtime/utils/codeHighlightStyle.d.ts +15 -0
  42. package/dist/runtime/utils/codeHighlightStyle.js +34 -0
  43. package/dist/runtime/utils/loadCodeMirror.d.ts +1 -0
  44. package/dist/runtime/utils/loadCodeMirror.js +6 -3
  45. package/package.json +2 -1
@@ -20,16 +20,16 @@ type __VLS_Props = {
20
20
  };
21
21
  };
22
22
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
23
+ prev: () => any;
23
24
  next: () => any;
24
25
  "update:viewMode": (mode: CalendarViewMode) => any;
25
- prev: () => any;
26
26
  today: () => any;
27
27
  "add-event": () => any;
28
28
  "navigate-to-month": (year: number, month: number) => any;
29
29
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
30
+ onPrev?: (() => any) | undefined;
30
31
  onNext?: (() => any) | undefined;
31
32
  "onUpdate:viewMode"?: ((mode: CalendarViewMode) => any) | undefined;
32
- onPrev?: (() => any) | undefined;
33
33
  onToday?: (() => any) | undefined;
34
34
  "onAdd-event"?: (() => any) | undefined;
35
35
  "onNavigate-to-month"?: ((year: number, month: number) => any) | undefined;
@@ -20,16 +20,16 @@ type __VLS_Props = {
20
20
  };
21
21
  };
22
22
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
23
+ prev: () => any;
23
24
  next: () => any;
24
25
  "update:viewMode": (mode: CalendarViewMode) => any;
25
- prev: () => any;
26
26
  today: () => any;
27
27
  "add-event": () => any;
28
28
  "navigate-to-month": (year: number, month: number) => any;
29
29
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
30
+ onPrev?: (() => any) | undefined;
30
31
  onNext?: (() => any) | undefined;
31
32
  "onUpdate:viewMode"?: ((mode: CalendarViewMode) => any) | undefined;
32
- onPrev?: (() => any) | undefined;
33
33
  onToday?: (() => any) | undefined;
34
34
  "onAdd-event"?: (() => any) | undefined;
35
35
  "onNavigate-to-month"?: ((year: number, month: number) => any) | undefined;
@@ -8,13 +8,13 @@ type __VLS_Props = {
8
8
  isLoading: boolean;
9
9
  };
10
10
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
- next: () => any;
12
11
  prev: () => any;
12
+ next: () => any;
13
13
  togglePlay: () => any;
14
14
  seek: (position: number) => any;
15
15
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
- onNext?: (() => any) | undefined;
17
16
  onPrev?: (() => any) | undefined;
17
+ onNext?: (() => any) | undefined;
18
18
  onTogglePlay?: (() => any) | undefined;
19
19
  onSeek?: ((position: number) => any) | undefined;
20
20
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -8,13 +8,13 @@ type __VLS_Props = {
8
8
  isLoading: boolean;
9
9
  };
10
10
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
- next: () => any;
12
11
  prev: () => any;
12
+ next: () => any;
13
13
  togglePlay: () => any;
14
14
  seek: (position: number) => any;
15
15
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
- onNext?: (() => any) | undefined;
17
16
  onPrev?: (() => any) | undefined;
17
+ onNext?: (() => any) | undefined;
18
18
  onTogglePlay?: (() => any) | undefined;
19
19
  onSeek?: ((position: number) => any) | undefined;
20
20
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -3,16 +3,17 @@ export interface DocLinkPickResult {
3
3
  label: string;
4
4
  }
5
5
  /**
6
- * Manages the state for the document picker dialog.
7
- * Used by doc-embed extension and inline link insertion.
6
+ * Imperative command-palette document picker.
8
7
  *
9
- * The module does not ship a modal component the consuming app provides
10
- * one and wires it up to the `pending` state. When the app calls `resolve()`
11
- * or `cancel()`, the promise returned by `pickDoc()` settles.
8
+ * Opens `<ADocPickModal>` (a `UCommandPalette` in a `UModal`) via Nuxt UI's
9
+ * overlay manager and resolves with the chosen doc, or `null` if dismissed.
10
+ * Self-contained — callers (the slash doc-embed/doc-link handlers, the
11
+ * `<ADocLinkPopover>` toolbar button, the drag handle) just `await pickDoc()`;
12
+ * no host wiring required. This is the picker used everywhere EXCEPT the inline
13
+ * `[[` / `![[` typing triggers (those commit directly via `<ADocSuggestMenu>`).
14
+ *
15
+ * Mirrors cou-sh/app/composables/useDocLinkPick.ts.
12
16
  */
13
17
  export declare function useDocLinkPick(): {
14
- isOpen: import("vue").Ref<boolean, boolean>;
15
18
  pickDoc: () => Promise<DocLinkPickResult | null>;
16
- resolve: (result: DocLinkPickResult) => void;
17
- cancel: () => void;
18
19
  };
@@ -1,22 +1,11 @@
1
- import { ref } from "vue";
1
+ import { useOverlay } from "#imports";
2
+ import ADocPickModal from "../components/ADocPickModal.vue";
2
3
  export function useDocLinkPick() {
3
- const isOpen = ref(false);
4
- let _resolve = null;
4
+ const overlay = useOverlay();
5
+ const modal = overlay.create(ADocPickModal);
5
6
  function pickDoc() {
6
- return new Promise((resolve2) => {
7
- _resolve = resolve2;
8
- isOpen.value = true;
9
- });
7
+ const { result } = modal.open();
8
+ return result;
10
9
  }
11
- function resolve(result) {
12
- isOpen.value = false;
13
- _resolve?.(result);
14
- _resolve = null;
15
- }
16
- function cancel() {
17
- isOpen.value = false;
18
- _resolve?.(null);
19
- _resolve = null;
20
- }
21
- return { isOpen, pickDoc, resolve, cancel };
10
+ return { pickDoc };
22
11
  }
@@ -0,0 +1,34 @@
1
+ import type { useChildTree } from './useChildTree.js';
2
+ type Tree = ReturnType<typeof useChildTree>;
3
+ export interface DocSuggestItem {
4
+ id: string;
5
+ label: string;
6
+ icon: string;
7
+ /** Dimmed ancestor path (e.g. "Kanban Board / Todo") for nesting context. */
8
+ prefix?: string;
9
+ /** When true, selecting this item creates a new child doc named `label`. */
10
+ isCreate?: boolean;
11
+ }
12
+ /** Reactive state shared between the DocSuggest extension and its popup. */
13
+ export interface DocSuggestPopupState {
14
+ active: boolean;
15
+ mode: 'link' | 'embed';
16
+ query: string;
17
+ items: DocSuggestItem[];
18
+ index: number;
19
+ rect: {
20
+ left: number;
21
+ top: number;
22
+ bottom: number;
23
+ } | null;
24
+ onSelect: ((item: DocSuggestItem) => void) | null;
25
+ }
26
+ export declare function emptyDocSuggestState(): DocSuggestPopupState;
27
+ /**
28
+ * Build a whole-space doc search for the `[[` / `![[` popups. Ranks entries by
29
+ * label match and appends a "Create …" item when the query has no exact match.
30
+ * Each result carries a dimmed ancestor-path `prefix` for nesting context — the
31
+ * popup stays a flat, searchable list (no tree drill-down).
32
+ */
33
+ export declare function makeDocSearch(tree: Tree, currentDocId: string): (rawQuery: string) => DocSuggestItem[];
34
+ export {};
@@ -0,0 +1,56 @@
1
+ import { resolveDocType } from "../utils/docTypes.js";
2
+ export function emptyDocSuggestState() {
3
+ return { active: false, mode: "link", query: "", items: [], index: 0, rect: null, onSelect: null };
4
+ }
5
+ const MAX_RESULTS = 20;
6
+ export function makeDocSearch(tree, currentDocId) {
7
+ return (rawQuery) => {
8
+ const query = rawQuery.trim();
9
+ const q = query.toLowerCase();
10
+ const entries = tree.entries.value;
11
+ const byId = new Map(entries.map((e) => [e.id, e]));
12
+ const pathOf = (parentId) => {
13
+ const out = [];
14
+ const seen = /* @__PURE__ */ new Set();
15
+ let pid = parentId;
16
+ while (pid && byId.has(pid) && !seen.has(pid)) {
17
+ seen.add(pid);
18
+ const e = byId.get(pid);
19
+ out.unshift(e.label || "Untitled");
20
+ pid = e.parentId;
21
+ }
22
+ return out.join(" / ");
23
+ };
24
+ const scored = [];
25
+ for (const e of entries) {
26
+ if (e.id === currentDocId) continue;
27
+ const label = (e.label || "").trim();
28
+ if (!label) continue;
29
+ const lower = label.toLowerCase();
30
+ let score = -1;
31
+ if (!q) score = 0;
32
+ else if (lower === q) score = 3;
33
+ else if (lower.startsWith(q)) score = 2;
34
+ else if (lower.includes(q)) score = 1;
35
+ if (score < 0) continue;
36
+ scored.push({ entry: e, score });
37
+ }
38
+ scored.sort((a, b) => b.score - a.score || a.entry.label.localeCompare(b.entry.label));
39
+ const items = scored.slice(0, MAX_RESULTS).map(({ entry }) => ({
40
+ id: entry.id,
41
+ label: entry.label,
42
+ icon: resolveDocType(entry.type).icon,
43
+ prefix: pathOf(entry.parentId) || void 0
44
+ }));
45
+ const hasExact = items.some((i) => i.label.toLowerCase() === q);
46
+ if (query && !hasExact) {
47
+ items.push({
48
+ id: "__create__",
49
+ label: `Create "${query}"`,
50
+ icon: "i-lucide-file-plus",
51
+ isCreate: true
52
+ });
53
+ }
54
+ return items;
55
+ };
56
+ }
@@ -1,3 +1,4 @@
1
+ import type { Ref } from 'vue';
1
2
  /**
2
3
  * Mobile-compatible drag & drop using pointer events.
3
4
  * Works on both touch and mouse devices, replacing HTML5 DragEvent handlers.
@@ -26,11 +27,27 @@ export declare function useTouchDrag(opts: {
26
27
  idAttr?: string;
27
28
  /** Data attribute name for containers (default: 'data-drop-container') */
28
29
  containerAttr?: string;
30
+ /**
31
+ * Navigate by range (prev/next) when the drag ghost dwells near the
32
+ * horizontal edges of `el` — e.g. a calendar dragging an event to the left
33
+ * edge steps to the previous period. Fires on a repeating interval while held
34
+ * at the edge, so a quick pass-through (or a drop on an edge cell) doesn't
35
+ * trigger it; resets the moment the pointer leaves the edge zone.
36
+ */
37
+ edgeNav?: {
38
+ el: Ref<HTMLElement | null>;
39
+ onPrev: () => void;
40
+ onNext: () => void;
41
+ /** px from the edge that counts as "at the edge" (default 48) */
42
+ threshold?: number;
43
+ /** ms between repeated nav steps while held at an edge (default 700) */
44
+ intervalMs?: number;
45
+ };
29
46
  }): {
30
- dragId: import("vue").Ref<string | null, string | null>;
31
- dragOverId: import("vue").Ref<string | null, string | null>;
32
- dragOverContainer: import("vue").Ref<string | null, string | null>;
33
- isDragging: import("vue").Ref<boolean, boolean>;
47
+ dragId: Ref<string | null, string | null>;
48
+ dragOverId: Ref<string | null, string | null>;
49
+ dragOverContainer: Ref<string | null, string | null>;
50
+ isDragging: Ref<boolean, boolean>;
34
51
  handlePointerDown: (e: PointerEvent, id: string) => void;
35
52
  cancel: () => void;
36
53
  };
@@ -7,6 +7,33 @@ export function useTouchDrag(opts) {
7
7
  const dragOverId = ref(null);
8
8
  const dragOverContainer = ref(null);
9
9
  const isDragging = ref(false);
10
+ let edgeNavTimer = null;
11
+ let edgeNavDir = null;
12
+ function clearEdgeNav() {
13
+ if (edgeNavTimer) {
14
+ clearInterval(edgeNavTimer);
15
+ edgeNavTimer = null;
16
+ }
17
+ edgeNavDir = null;
18
+ }
19
+ function handleEdgeNav(clientX) {
20
+ const cfg = opts.edgeNav;
21
+ const el = cfg?.el.value;
22
+ if (!cfg || !el) return;
23
+ const rect = el.getBoundingClientRect();
24
+ const threshold = cfg.threshold ?? 48;
25
+ let dir = null;
26
+ if (clientX <= rect.left + threshold) dir = "prev";
27
+ else if (clientX >= rect.right - threshold) dir = "next";
28
+ if (dir === edgeNavDir) return;
29
+ clearEdgeNav();
30
+ if (!dir) return;
31
+ edgeNavDir = dir;
32
+ edgeNavTimer = setInterval(() => {
33
+ if (edgeNavDir === "prev") cfg.onPrev();
34
+ else if (edgeNavDir === "next") cfg.onNext();
35
+ }, cfg.intervalMs ?? 700);
36
+ }
10
37
  let startX = 0;
11
38
  let startY = 0;
12
39
  let delayTimer = null;
@@ -131,6 +158,7 @@ export function useTouchDrag(opts) {
131
158
  opts.onMoveOverContainer(dragId.value, container, e);
132
159
  }
133
160
  autoScroll(e.clientY);
161
+ handleEdgeNav(e.clientX);
134
162
  }
135
163
  function autoScroll(clientY) {
136
164
  const threshold = 60;
@@ -143,6 +171,7 @@ export function useTouchDrag(opts) {
143
171
  }
144
172
  function onDocPointerUp() {
145
173
  cancelPending();
174
+ clearEdgeNav();
146
175
  document.removeEventListener("pointermove", onDocPointerMove);
147
176
  document.removeEventListener("pointerup", onDocPointerUp);
148
177
  document.removeEventListener("pointercancel", onDocPointerUp);
@@ -175,6 +204,7 @@ export function useTouchDrag(opts) {
175
204
  dragOverContainer.value = null;
176
205
  isDragging.value = false;
177
206
  pendingId = null;
207
+ clearEdgeNav();
178
208
  if (delayTimer) {
179
209
  clearTimeout(delayTimer);
180
210
  delayTimer = null;
@@ -14,7 +14,7 @@ export const DocLinkDrop = Extension.create({
14
14
  const de = event;
15
15
  if (!de.dataTransfer?.types.includes(DOC_DRAG_MIME)) return false;
16
16
  de.preventDefault();
17
- de.dataTransfer.dropEffect = de.altKey ? "link" : "copy";
17
+ de.dataTransfer.dropEffect = de.metaKey || de.ctrlKey ? "link" : "copy";
18
18
  return false;
19
19
  },
20
20
  drop(view, event) {
@@ -38,7 +38,7 @@ export const DocLinkDrop = Extension.create({
38
38
  const dropPos = view.posAtCoords({ left: de.clientX, top: de.clientY })?.pos;
39
39
  if (dropPos == null) return true;
40
40
  const { schema, tr } = view.state;
41
- if (de.altKey) {
41
+ if (de.metaKey || de.ctrlKey) {
42
42
  const linkType = schema.nodes.docLink;
43
43
  if (!linkType) return true;
44
44
  const node = linkType.create({ docId: data.id });
@@ -0,0 +1,28 @@
1
+ import { Extension } from '@tiptap/core';
2
+ import type { DocSuggestItem, DocSuggestPopupState } from '../composables/useDocSuggest.js';
3
+ export interface DocSuggestOptions {
4
+ /** Whole-space doc search (label match + create-on-miss). */
5
+ search: (query: string) => DocSuggestItem[];
6
+ /** Commit the chosen item as a doc link or embed over `range`. */
7
+ onCommit: (item: DocSuggestItem, mode: 'link' | 'embed', range: {
8
+ from: number;
9
+ to: number;
10
+ }, query: string) => void;
11
+ }
12
+ export interface DocSuggestStorage {
13
+ popup: DocSuggestPopupState;
14
+ }
15
+ /**
16
+ * Inline `[[` (doc link) and `![[` (doc embed) suggestion popups — the
17
+ * mention-style counterpart for cross-document references.
18
+ *
19
+ * One `@tiptap/suggestion` plugin triggers on `[[`; the leading `!` (when
20
+ * present) is what distinguishes embed from link. The popup is driven through
21
+ * `editor.storage.docSuggest.popup` (a per-editor reactive object) which the
22
+ * `ADocSuggestMenu` component renders — so nested/embedded editors never share
23
+ * or duplicate one popup.
24
+ *
25
+ * Ported from cou-sh/app/extensions/doc-suggest.ts.
26
+ */
27
+ export declare const DocSuggest: Extension<DocSuggestOptions, DocSuggestStorage>;
28
+ export default DocSuggest;
@@ -0,0 +1,85 @@
1
+ import { Extension } from "@tiptap/core";
2
+ import { reactive } from "vue";
3
+ import { Suggestion } from "@tiptap/suggestion";
4
+ import { emptyDocSuggestState } from "../composables/useDocSuggest.js";
5
+ export const DocSuggest = Extension.create({
6
+ name: "docSuggest",
7
+ addOptions() {
8
+ return {
9
+ search: () => [],
10
+ onCommit: () => {
11
+ }
12
+ };
13
+ },
14
+ addStorage() {
15
+ return { popup: reactive(emptyDocSuggestState()) };
16
+ },
17
+ addProseMirrorPlugins() {
18
+ const reset = () => Object.assign(this.storage.popup, emptyDocSuggestState());
19
+ return [
20
+ Suggestion({
21
+ editor: this.editor,
22
+ char: "[[",
23
+ startOfLine: false,
24
+ allowSpaces: true,
25
+ allowedPrefixes: null,
26
+ // allow triggering after `!` and mid-line
27
+ items: ({ query }) => this.options.search(query),
28
+ command: () => {
29
+ },
30
+ // selection handled via the popup / onKeyDown
31
+ render: () => {
32
+ const sync = (props) => {
33
+ const popup = this.storage.popup;
34
+ const from = props.range.from;
35
+ const before = props.editor.state.doc.textBetween(Math.max(0, from - 1), from);
36
+ const mode = before === "!" ? "embed" : "link";
37
+ const rect = props.clientRect?.();
38
+ popup.active = true;
39
+ popup.mode = mode;
40
+ popup.query = props.query;
41
+ popup.items = props.items;
42
+ popup.index = 0;
43
+ popup.rect = rect ? { left: rect.left, top: rect.top, bottom: rect.bottom } : null;
44
+ popup.onSelect = (item) => {
45
+ const realBefore = props.editor.state.doc.textBetween(Math.max(0, from - 1), from);
46
+ const realMode = realBefore === "!" ? "embed" : "link";
47
+ const realFrom = realMode === "embed" ? from - 1 : from;
48
+ this.options.onCommit(item, realMode, { from: realFrom, to: props.range.to }, props.query);
49
+ reset();
50
+ };
51
+ };
52
+ return {
53
+ onStart: sync,
54
+ onUpdate: sync,
55
+ onKeyDown: ({ event }) => {
56
+ const popup = this.storage.popup;
57
+ const items = popup.items;
58
+ if (event.key === "Escape") {
59
+ reset();
60
+ return true;
61
+ }
62
+ if (!items.length) return false;
63
+ if (event.key === "ArrowDown") {
64
+ popup.index = (popup.index + 1) % items.length;
65
+ return true;
66
+ }
67
+ if (event.key === "ArrowUp") {
68
+ popup.index = (popup.index - 1 + items.length) % items.length;
69
+ return true;
70
+ }
71
+ if (event.key === "Enter") {
72
+ const item = items[popup.index];
73
+ if (item) popup.onSelect?.(item);
74
+ return true;
75
+ }
76
+ return false;
77
+ },
78
+ onExit: () => reset()
79
+ };
80
+ }
81
+ })
82
+ ];
83
+ }
84
+ });
85
+ export default DocSuggest;
@@ -4,6 +4,7 @@ import { NodeViewWrapper } from "@tiptap/vue-3";
4
4
  import AColorPalettePopover from "../../components/editor/AColorPalettePopover.vue";
5
5
  import AIconPickerPopover from "../../components/editor/AIconPickerPopover.vue";
6
6
  import ALocationPickerPopover from "../../components/editor/ALocationPickerPopover.vue";
7
+ import AMetaNumberStepper from "../../components/editor/AMetaNumberStepper.vue";
7
8
  import { useAbraLocale } from "../../composables/useAbraLocale";
8
9
  import {
9
10
  DateFormatter,
@@ -62,6 +63,8 @@ const sliderMin = computed(() => props.node.attrs.sliderMin ?? 0);
62
63
  const sliderMax = computed(() => props.node.attrs.sliderMax ?? 100);
63
64
  const sliderStep = computed(() => props.node.attrs.sliderStep ?? 1);
64
65
  const unit = computed(() => props.node.attrs.unit ?? "");
66
+ const numMin = computed(() => props.node.attrs.sliderMin);
67
+ const numMax = computed(() => props.node.attrs.sliderMax);
65
68
  function getStr(key) {
66
69
  return storage()?.pageMeta?.[key] ?? "";
67
70
  }
@@ -239,16 +242,6 @@ function onRatingWheel(e) {
239
242
  const next = Math.max(0, Math.min(max, current + delta));
240
243
  if (next !== current) patch({ [metaKey.value]: next });
241
244
  }
242
- function onNumberWheel(e) {
243
- const delta = -Math.sign(e.deltaY);
244
- if (!delta) return;
245
- const step = (sliderStep.value || 1) * (e.shiftKey ? 10 : 1);
246
- const current = getNum(metaKey.value);
247
- const min = sliderMin.value;
248
- const max = sliderMax.value;
249
- const next = Math.max(min, Math.min(max, current + delta * step));
250
- if (next !== current) patch({ [metaKey.value]: next });
251
- }
252
245
  function onSliderWheel(e) {
253
246
  const delta = -Math.sign(e.deltaY);
254
247
  if (!delta) return;
@@ -784,13 +777,20 @@ function removeOption(opt) {
784
777
 
785
778
  <!-- ── number ─────────────────────────────────────────────────────────── -->
786
779
  <template v-else-if="fieldType === 'number'">
787
- <div
788
- class="flex items-center gap-1.5 h-7 px-2.5 border border-(--ui-border) rounded-md text-sm"
789
- @mousedown.stop
790
- @touchstart.stop
791
- @wheel.prevent="onNumberWheel"
780
+ <AMetaNumberStepper
781
+ class="min-w-40"
782
+ :model-value="getOptNum(metaKey)"
783
+ :min="numMin"
784
+ :max="numMax"
785
+ :step="sliderStep"
786
+ :unit="unit"
787
+ :disabled="!isEditable"
788
+ @update:model-value="onNumber"
792
789
  >
793
- <template v-if="fieldLabel">
790
+ <template
791
+ v-if="fieldLabel"
792
+ #label
793
+ >
794
794
  <input
795
795
  v-if="editingLabel"
796
796
  ref="labelInputEl"
@@ -807,18 +807,7 @@ function removeOption(opt) {
807
807
  @click.stop="startEditLabel"
808
808
  >{{ fieldLabel }}</span>
809
809
  </template>
810
- <UInputNumber
811
- size="xs"
812
- :model-value="getOptNum(metaKey)"
813
- :step="sliderStep"
814
- class="max-w-32"
815
- @update:model-value="onNumber"
816
- />
817
- <span
818
- v-if="unit"
819
- class="text-(--ui-text-muted) shrink-0 text-xs"
820
- >{{ unit }}</span>
821
- </div>
810
+ </AMetaNumberStepper>
822
811
  </template>
823
812
 
824
813
  <!-- ── toggle ─────────────────────────────────────────────────────────── -->
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Atom One — Nuxt UI syntax highlighting.
3
+ *
4
+ * Keeps Atom One Dark's familiar token roles but pulls each colour from the
5
+ * host's Nuxt UI theme tokens (`--ui-*` / `--color-*`), so the code editor
6
+ * matches the surrounding UI. Every value falls back to the canonical Atom
7
+ * One hex when the token isn't defined.
8
+ *
9
+ * A factory (not a const) because CodeMirror is lazy-loaded — `HighlightStyle`
10
+ * and the lezer `tags` come from the bundle resolved in `loadCodeMirror.ts`.
11
+ */
12
+ type HighlightStyleStatic = (typeof import('@codemirror/language'))['HighlightStyle'];
13
+ type TagsObject = (typeof import('@lezer/highlight'))['tags'];
14
+ export declare function buildAtomOneNuxtHighlight(HighlightStyleCtor: HighlightStyleStatic, t: TagsObject): import("@codemirror/language").HighlightStyle;
15
+ export {};
@@ -0,0 +1,34 @@
1
+ export function buildAtomOneNuxtHighlight(HighlightStyleCtor, t) {
2
+ return HighlightStyleCtor.define([
3
+ // Comments — dimmed + italic.
4
+ { tag: [t.comment, t.lineComment, t.blockComment, t.docComment], color: "var(--ui-text-dimmed, #5c6370)", fontStyle: "italic" },
5
+ // Keywords / storage — brand colour (Atom One purple fallback).
6
+ { tag: [t.keyword, t.moduleKeyword, t.controlKeyword, t.operatorKeyword, t.definitionKeyword, t.self], color: "var(--ui-primary, #c678dd)" },
7
+ // Strings + escapes — green.
8
+ { tag: [t.string, t.special(t.string), t.docString, t.regexp, t.attributeValue], color: "var(--color-green-400, #98c379)" },
9
+ { tag: [t.escape, t.character], color: "var(--color-cyan-400, #56b6c2)" },
10
+ // Numbers / constants / booleans — orange.
11
+ { tag: [t.number, t.integer, t.float, t.bool, t.null, t.atom, t.unit, t.constant(t.name), t.standard(t.name)], color: "var(--color-orange-400, #d19a66)" },
12
+ // Functions / methods — blue.
13
+ { tag: [t.function(t.variableName), t.function(t.propertyName), t.labelName, t.macroName], color: "var(--color-blue-400, #61afef)" },
14
+ // Types / classes / namespaces — amber.
15
+ { tag: [t.typeName, t.className, t.namespace, t.definition(t.typeName)], color: "var(--color-amber-400, #e5c07b)" },
16
+ // Properties / attributes / tags — red.
17
+ { tag: [t.propertyName, t.attributeName, t.tagName], color: "var(--color-red-400, #e06c75)" },
18
+ // Plain identifiers — body text colour.
19
+ { tag: [t.variableName, t.definition(t.variableName)], color: "var(--ui-text-highlighted, #abb2bf)" },
20
+ // Operators — cyan.
21
+ { tag: [t.operator, t.derefOperator, t.arithmeticOperator, t.logicOperator, t.bitwiseOperator, t.compareOperator, t.updateOperator], color: "var(--color-cyan-400, #56b6c2)" },
22
+ // Punctuation / brackets — muted.
23
+ { tag: [t.punctuation, t.separator, t.bracket, t.angleBracket, t.squareBracket, t.paren, t.brace], color: "var(--ui-text-muted, #abb2bf)" },
24
+ // Meta / annotations — cyan.
25
+ { tag: [t.meta, t.documentMeta, t.annotation, t.processingInstruction], color: "var(--color-cyan-400, #56b6c2)" },
26
+ // Markdown / prose niceties.
27
+ { tag: [t.heading], fontWeight: "600", color: "var(--color-red-400, #e06c75)" },
28
+ { tag: [t.strong], fontWeight: "600" },
29
+ { tag: [t.emphasis], fontStyle: "italic" },
30
+ { tag: [t.strikethrough], textDecoration: "line-through" },
31
+ { tag: [t.link, t.url], color: "var(--ui-primary, #61afef)", textDecoration: "underline" },
32
+ { tag: [t.invalid], color: "var(--ui-error, #e06c75)" }
33
+ ]);
34
+ }
@@ -28,5 +28,6 @@ export interface CodeMirrorBundle {
28
28
  langVue: typeof import('@codemirror/lang-vue');
29
29
  langJson: typeof import('@codemirror/lang-json');
30
30
  yCollab: typeof import('y-codemirror.next');
31
+ lezerHighlight: typeof import('@lezer/highlight');
31
32
  }
32
33
  export declare function loadCodeMirror(): Promise<CodeMirrorBundle | null>;
@@ -17,7 +17,8 @@ export async function loadCodeMirror() {
17
17
  "@codemirror/lang-css",
18
18
  "@codemirror/lang-vue",
19
19
  "@codemirror/lang-json",
20
- "y-codemirror.next"
20
+ "y-codemirror.next",
21
+ "@lezer/highlight"
21
22
  ];
22
23
  const [
23
24
  view,
@@ -30,7 +31,8 @@ export async function loadCodeMirror() {
30
31
  langCss,
31
32
  langVue,
32
33
  langJson,
33
- yCollab
34
+ yCollab,
35
+ lezerHighlight
34
36
  ] = await Promise.all(names.map((n) => import(
35
37
  /* @vite-ignore */
36
38
  n
@@ -46,7 +48,8 @@ export async function loadCodeMirror() {
46
48
  langCss,
47
49
  langVue,
48
50
  langJson,
49
- yCollab
51
+ yCollab,
52
+ lezerHighlight
50
53
  };
51
54
  return cache;
52
55
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abraca/nuxt",
3
- "version": "2.13.0",
3
+ "version": "2.15.0",
4
4
  "description": "First-class Nuxt module for the Abracadabra CRDT collaboration platform",
5
5
  "repository": "abracadabra/abracadabra-nuxt",
6
6
  "license": "MIT",
@@ -69,6 +69,7 @@
69
69
  "@tiptap/extension-text-align": "^3.0.0",
70
70
  "@tiptap/extension-text-style": "^3.0.0",
71
71
  "@tiptap/starter-kit": "^3.0.0",
72
+ "@tiptap/suggestion": "^3.0.0",
72
73
  "@tiptap/vue-3": "^3.0.0",
73
74
  "@unovis/ts": "^1.6.5",
74
75
  "@unovis/vue": "^1.6.5",