@signal9/era-ui 12.1.2 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev/audit-overlay.svelte +1 -1
- package/dist/docs/css-utilities.js +6 -1
- package/dist/docs/notes.md +12 -11
- package/dist/era-ui.css +1 -1
- package/dist/generated-docs/badge.md +3 -0
- package/dist/generated-docs/button.md +4 -0
- package/dist/generated-docs/chip.md +3 -0
- package/dist/generated-docs/llms-full.txt +53 -31
- package/dist/generated-docs/llms.txt +1 -1
- package/dist/generated-docs/manifest.json +17 -17
- package/dist/generated-docs/notes.md +12 -11
- package/dist/generated-docs/utilities.json +10 -10
- package/dist/generated-docs/utilities.md +30 -19
- package/dist/os/index.d.ts +0 -1
- package/dist/os/index.js +0 -1
- package/dist/os/notification-center.svelte +1 -2
- package/dist/os/taskbar.svelte +2 -3
- package/dist/os/toaster.svelte +2 -3
- package/dist/styles/index.css +77 -12
- package/dist/styles/layers.css +48 -0
- package/dist/styles/surfaces/base.css +4 -2
- package/dist/styles/surfaces/glass.css +0 -13
- package/dist/theme/index.d.ts +87 -0
- package/dist/theme/index.js +86 -0
- package/dist/ui/alert-dialog/alert-dialog-content.svelte +2 -2
- package/dist/ui/alert-dialog/alert-dialog-overlay.svelte +1 -1
- package/dist/ui/badge/badge.svelte +14 -1
- package/dist/ui/badge/badge.svelte.d.ts +4 -0
- package/dist/ui/button/button.svelte +37 -7
- package/dist/ui/button/button.svelte.d.ts +6 -0
- package/dist/ui/chip/chip.svelte +14 -1
- package/dist/ui/chip/chip.svelte.d.ts +4 -0
- package/dist/ui/combobox/combobox-content.svelte +1 -1
- package/dist/ui/command-bar/command-bar.svelte +1 -1
- package/dist/ui/context-menu/context-menu-content.svelte +1 -1
- package/dist/ui/context-menu/context-menu-sub-content.svelte +1 -1
- package/dist/ui/date-picker/date-picker-content.svelte +1 -1
- package/dist/ui/date-range-picker/date-range-picker-content.svelte +1 -1
- package/dist/ui/dialog/dialog-content.svelte +1 -1
- package/dist/ui/dialog/dialog-overlay.svelte +5 -1
- package/dist/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
- package/dist/ui/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
- package/dist/ui/link-preview/link-preview-content.svelte +1 -1
- package/dist/ui/menubar/menubar-content.svelte +1 -1
- package/dist/ui/menubar/menubar-sub-content.svelte +1 -1
- package/dist/ui/popover/popover-content.svelte +1 -1
- package/dist/ui/select/select-content.svelte +1 -1
- package/dist/ui/sheet/sheet-content.svelte +1 -1
- package/dist/ui/sheet/sheet-content.svelte.d.ts +1 -1
- package/dist/ui/tooltip/tooltip-content.svelte +1 -1
- package/package.json +9 -12
- package/dist/apps/index.d.ts +0 -20
- package/dist/apps/index.js +0 -20
- package/dist/apps/llm-shell/index.d.ts +0 -2
- package/dist/apps/llm-shell/index.js +0 -1
- package/dist/apps/llm-shell/llm-shell.svelte +0 -334
- package/dist/apps/llm-shell/llm-shell.svelte.d.ts +0 -57
- package/dist/apps/llm-shell/llm-swarm.svelte +0 -79
- package/dist/apps/llm-shell/llm-swarm.svelte.d.ts +0 -25
- package/dist/apps/llm-shell/types.d.ts +0 -103
- package/dist/apps/llm-shell/types.js +0 -1
- package/dist/apps/notes/editor/bubble-menu.svelte.d.ts +0 -11
- package/dist/apps/notes/editor/bubble-menu.svelte.js +0 -165
- package/dist/apps/notes/editor/bubble-toolbar.svelte +0 -55
- package/dist/apps/notes/editor/bubble-toolbar.svelte.d.ts +0 -22
- package/dist/apps/notes/editor/extensions.d.ts +0 -18
- package/dist/apps/notes/editor/extensions.js +0 -88
- package/dist/apps/notes/editor/floating.d.ts +0 -25
- package/dist/apps/notes/editor/floating.js +0 -54
- package/dist/apps/notes/editor/link.d.ts +0 -9
- package/dist/apps/notes/editor/link.js +0 -22
- package/dist/apps/notes/editor/list-cleanup-rule.d.ts +0 -14
- package/dist/apps/notes/editor/list-cleanup-rule.js +0 -47
- package/dist/apps/notes/editor/slash-command.svelte.d.ts +0 -22
- package/dist/apps/notes/editor/slash-command.svelte.js +0 -215
- package/dist/apps/notes/editor/slash-menu.svelte +0 -82
- package/dist/apps/notes/editor/slash-menu.svelte.d.ts +0 -23
- package/dist/apps/notes/index.d.ts +0 -11
- package/dist/apps/notes/index.js +0 -13
- package/dist/apps/notes/markdown.d.ts +0 -12
- package/dist/apps/notes/markdown.js +0 -165
- package/dist/apps/notes/note-editor.svelte +0 -176
- package/dist/apps/notes/note-editor.svelte.d.ts +0 -36
- package/dist/apps/notes/notes-store.svelte.d.ts +0 -64
- package/dist/apps/notes/notes-store.svelte.js +0 -234
- package/dist/apps/notes/notes.svelte +0 -501
- package/dist/apps/notes/notes.svelte.d.ts +0 -53
- package/dist/apps/notes/tree.d.ts +0 -70
- package/dist/apps/notes/tree.js +0 -185
- package/dist/apps/notes/types.d.ts +0 -49
- package/dist/apps/notes/types.js +0 -11
- package/dist/os/layers.d.ts +0 -23
- package/dist/os/layers.js +0 -23
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The one link behaviour, shared by the bubble toolbar and Mod-K.
|
|
3
|
-
*
|
|
4
|
-
* Three cases, in order: an existing link is removed; a selection that is
|
|
5
|
-
* already a bare URL links to itself (the common paste-then-select gesture);
|
|
6
|
-
* anything else asks for a target.
|
|
7
|
-
*/
|
|
8
|
-
export function toggleLink(editor) {
|
|
9
|
-
if (editor.isActive('link')) {
|
|
10
|
-
editor.chain().focus().unsetLink().run();
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
const { from, to } = editor.state.selection;
|
|
14
|
-
const selected = editor.state.doc.textBetween(from, to);
|
|
15
|
-
if (/^https?:\/\/\S+$/.test(selected)) {
|
|
16
|
-
editor.chain().focus().setLink({ href: selected }).run();
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
const url = globalThis.prompt?.('Enter URL:');
|
|
20
|
-
if (url)
|
|
21
|
-
editor.chain().focus().setLink({ href: url }).run();
|
|
22
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { Extension } from '@tiptap/core';
|
|
2
|
-
/**
|
|
3
|
-
* Strips redundant list prefixes typed INSIDE a list item.
|
|
4
|
-
*
|
|
5
|
-
* TipTap turns a leading `1. ` into an ordered list. The user then presses
|
|
6
|
-
* Enter and — as humans do — types `2. `, which is now literal text inside an
|
|
7
|
-
* already-numbered item: "2. 2. Walk the dog". Same for `- ` inside a bullet
|
|
8
|
-
* list.
|
|
9
|
-
*
|
|
10
|
-
* These rules fire only when the cursor is genuinely inside a list of the
|
|
11
|
-
* matching kind, so the ordinary wrapping rules that CREATE lists are left
|
|
12
|
-
* alone.
|
|
13
|
-
*/
|
|
14
|
-
export declare const ListCleanupRule: Extension<any, any>;
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { Extension, InputRule } from '@tiptap/core';
|
|
2
|
-
/**
|
|
3
|
-
* Strips redundant list prefixes typed INSIDE a list item.
|
|
4
|
-
*
|
|
5
|
-
* TipTap turns a leading `1. ` into an ordered list. The user then presses
|
|
6
|
-
* Enter and — as humans do — types `2. `, which is now literal text inside an
|
|
7
|
-
* already-numbered item: "2. 2. Walk the dog". Same for `- ` inside a bullet
|
|
8
|
-
* list.
|
|
9
|
-
*
|
|
10
|
-
* These rules fire only when the cursor is genuinely inside a list of the
|
|
11
|
-
* matching kind, so the ordinary wrapping rules that CREATE lists are left
|
|
12
|
-
* alone.
|
|
13
|
-
*/
|
|
14
|
-
export const ListCleanupRule = Extension.create({
|
|
15
|
-
name: 'listCleanupRule',
|
|
16
|
-
addInputRules() {
|
|
17
|
-
return [
|
|
18
|
-
// "N. " typed while already in an ordered list
|
|
19
|
-
new InputRule({
|
|
20
|
-
find: /^(\d+)\.\s$/,
|
|
21
|
-
handler: ({ state, range }) => {
|
|
22
|
-
const { $from } = state.selection;
|
|
23
|
-
for (let depth = $from.depth; depth > 0; depth--) {
|
|
24
|
-
if ($from.node(depth).type.name === 'orderedList') {
|
|
25
|
-
state.apply(state.tr.delete(range.from, range.to));
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
// Not in an ordered list — leave it to the wrapping rule.
|
|
30
|
-
}
|
|
31
|
-
}),
|
|
32
|
-
// "- " or "* " typed while already in a bullet list
|
|
33
|
-
new InputRule({
|
|
34
|
-
find: /^[-*]\s$/,
|
|
35
|
-
handler: ({ state, range }) => {
|
|
36
|
-
const { $from } = state.selection;
|
|
37
|
-
for (let depth = $from.depth; depth > 0; depth--) {
|
|
38
|
-
if ($from.node(depth).type.name === 'bulletList') {
|
|
39
|
-
state.apply(state.tr.delete(range.from, range.to));
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
];
|
|
46
|
-
}
|
|
47
|
-
});
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `/` slash-command menu.
|
|
3
|
-
*
|
|
4
|
-
* TipTap's Suggestion plugin is imperative — it hands you a `render()` object
|
|
5
|
-
* with `onStart`/`onUpdate`/`onKeyDown`/`onExit` and expects you to produce DOM.
|
|
6
|
-
* Rather than hand-build that DOM (and hand-maintain a second set of colours and
|
|
7
|
-
* spacings that would drift from the library the moment a token moved), this
|
|
8
|
-
* mounts a real Svelte component with `mount()` and feeds it a `$state` props
|
|
9
|
-
* object. The panel is therefore styled with era utilities like everything else
|
|
10
|
-
* and follows every axis automatically.
|
|
11
|
-
*/
|
|
12
|
-
import { Extension, type Editor, type Range } from '@tiptap/core';
|
|
13
|
-
import { type Component } from 'svelte';
|
|
14
|
-
import type { IconProps } from '@lucide/svelte';
|
|
15
|
-
export interface SlashCommandItem {
|
|
16
|
-
title: string;
|
|
17
|
-
description: string;
|
|
18
|
-
icon: Component<IconProps>;
|
|
19
|
-
command: (editor: Editor, range: Range) => void;
|
|
20
|
-
}
|
|
21
|
-
export declare const slashItems: SlashCommandItem[];
|
|
22
|
-
export declare const SlashCommand: Extension<any, any>;
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `/` slash-command menu.
|
|
3
|
-
*
|
|
4
|
-
* TipTap's Suggestion plugin is imperative — it hands you a `render()` object
|
|
5
|
-
* with `onStart`/`onUpdate`/`onKeyDown`/`onExit` and expects you to produce DOM.
|
|
6
|
-
* Rather than hand-build that DOM (and hand-maintain a second set of colours and
|
|
7
|
-
* spacings that would drift from the library the moment a token moved), this
|
|
8
|
-
* mounts a real Svelte component with `mount()` and feeds it a `$state` props
|
|
9
|
-
* object. The panel is therefore styled with era utilities like everything else
|
|
10
|
-
* and follows every axis automatically.
|
|
11
|
-
*/
|
|
12
|
-
import { Extension } from '@tiptap/core';
|
|
13
|
-
import Suggestion from '@tiptap/suggestion';
|
|
14
|
-
import { flushSync, mount, unmount } from 'svelte';
|
|
15
|
-
import Heading1 from '@lucide/svelte/icons/heading-1';
|
|
16
|
-
import Heading2 from '@lucide/svelte/icons/heading-2';
|
|
17
|
-
import Heading3 from '@lucide/svelte/icons/heading-3';
|
|
18
|
-
import List from '@lucide/svelte/icons/list';
|
|
19
|
-
import ListOrdered from '@lucide/svelte/icons/list-ordered';
|
|
20
|
-
import ListChecks from '@lucide/svelte/icons/list-checks';
|
|
21
|
-
import SquareCode from '@lucide/svelte/icons/square-code';
|
|
22
|
-
import Quote from '@lucide/svelte/icons/quote';
|
|
23
|
-
import TableIcon from '@lucide/svelte/icons/table';
|
|
24
|
-
import Minus from '@lucide/svelte/icons/minus';
|
|
25
|
-
import ListCollapse from '@lucide/svelte/icons/list-collapse';
|
|
26
|
-
import ImageIcon from '@lucide/svelte/icons/image';
|
|
27
|
-
import SlashMenu from './slash-menu.svelte';
|
|
28
|
-
import { createOverlayHost, placeBelow } from './floating.js';
|
|
29
|
-
export const slashItems = [
|
|
30
|
-
{
|
|
31
|
-
title: 'Heading 1',
|
|
32
|
-
description: 'Large heading',
|
|
33
|
-
icon: Heading1,
|
|
34
|
-
command: (editor, range) => editor.chain().focus().deleteRange(range).setHeading({ level: 1 }).run()
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
title: 'Heading 2',
|
|
38
|
-
description: 'Medium heading',
|
|
39
|
-
icon: Heading2,
|
|
40
|
-
command: (editor, range) => editor.chain().focus().deleteRange(range).setHeading({ level: 2 }).run()
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
title: 'Heading 3',
|
|
44
|
-
description: 'Small heading',
|
|
45
|
-
icon: Heading3,
|
|
46
|
-
command: (editor, range) => editor.chain().focus().deleteRange(range).setHeading({ level: 3 }).run()
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
title: 'Bullet List',
|
|
50
|
-
description: 'Unordered list',
|
|
51
|
-
icon: List,
|
|
52
|
-
command: (editor, range) => editor.chain().focus().deleteRange(range).toggleBulletList().run()
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
title: 'Numbered List',
|
|
56
|
-
description: 'Ordered list',
|
|
57
|
-
icon: ListOrdered,
|
|
58
|
-
command: (editor, range) => editor.chain().focus().deleteRange(range).toggleOrderedList().run()
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
title: 'Task List',
|
|
62
|
-
description: 'Checklist',
|
|
63
|
-
icon: ListChecks,
|
|
64
|
-
command: (editor, range) => editor.chain().focus().deleteRange(range).toggleTaskList().run()
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
title: 'Code Block',
|
|
68
|
-
description: 'Highlighted code',
|
|
69
|
-
icon: SquareCode,
|
|
70
|
-
command: (editor, range) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run()
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
title: 'Blockquote',
|
|
74
|
-
description: 'Quoted text',
|
|
75
|
-
icon: Quote,
|
|
76
|
-
command: (editor, range) => editor.chain().focus().deleteRange(range).toggleBlockquote().run()
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
title: 'Table',
|
|
80
|
-
description: '3×3 with header',
|
|
81
|
-
icon: TableIcon,
|
|
82
|
-
command: (editor, range) => editor
|
|
83
|
-
.chain()
|
|
84
|
-
.focus()
|
|
85
|
-
.deleteRange(range)
|
|
86
|
-
.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
|
|
87
|
-
.run()
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
title: 'Divider',
|
|
91
|
-
description: 'Horizontal rule',
|
|
92
|
-
icon: Minus,
|
|
93
|
-
command: (editor, range) => editor.chain().focus().deleteRange(range).setHorizontalRule().run()
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
title: 'Details',
|
|
97
|
-
description: 'Collapsible section',
|
|
98
|
-
icon: ListCollapse,
|
|
99
|
-
command: (editor, range) => editor.chain().focus().deleteRange(range).setDetails().run()
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
title: 'Image',
|
|
103
|
-
description: 'Insert by URL',
|
|
104
|
-
icon: ImageIcon,
|
|
105
|
-
command: (editor, range) => {
|
|
106
|
-
const url = globalThis.prompt?.('Image URL:');
|
|
107
|
-
if (url)
|
|
108
|
-
editor.chain().focus().deleteRange(range).setImage({ src: url }).run();
|
|
109
|
-
// Still consume the "/image" text even when the prompt is dismissed.
|
|
110
|
-
else
|
|
111
|
-
editor.chain().focus().deleteRange(range).run();
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
];
|
|
115
|
-
export const SlashCommand = Extension.create({
|
|
116
|
-
name: 'slashCommand',
|
|
117
|
-
addProseMirrorPlugins() {
|
|
118
|
-
return [
|
|
119
|
-
Suggestion({
|
|
120
|
-
editor: this.editor,
|
|
121
|
-
char: '/',
|
|
122
|
-
allowSpaces: false,
|
|
123
|
-
startOfLine: false,
|
|
124
|
-
items: ({ query }) => {
|
|
125
|
-
const q = query.toLowerCase();
|
|
126
|
-
return slashItems.filter((item) => item.title.toLowerCase().includes(q) || item.description.toLowerCase().includes(q));
|
|
127
|
-
},
|
|
128
|
-
command: ({ editor, range, props }) => {
|
|
129
|
-
props.command(editor, range);
|
|
130
|
-
},
|
|
131
|
-
render: () => {
|
|
132
|
-
let host = null;
|
|
133
|
-
let menu = null;
|
|
134
|
-
let run = null;
|
|
135
|
-
// The one object the mounted component reads. Mutating it is what
|
|
136
|
-
// re-renders the panel — there is no second render path.
|
|
137
|
-
const view = $state({
|
|
138
|
-
items: [],
|
|
139
|
-
selected: 0,
|
|
140
|
-
onSelect: (index) => {
|
|
141
|
-
const item = view.items[index];
|
|
142
|
-
if (item)
|
|
143
|
-
run?.(item);
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
/** Apply pending prop changes, THEN measure — the panel's height
|
|
147
|
-
* changes with the filtered set, and placement depends on it. */
|
|
148
|
-
function reposition(clientRect) {
|
|
149
|
-
if (!host || !clientRect)
|
|
150
|
-
return;
|
|
151
|
-
flushSync();
|
|
152
|
-
const rect = clientRect();
|
|
153
|
-
if (rect)
|
|
154
|
-
placeBelow(host, rect);
|
|
155
|
-
}
|
|
156
|
-
function move(delta) {
|
|
157
|
-
const n = view.items.length;
|
|
158
|
-
if (n === 0)
|
|
159
|
-
return;
|
|
160
|
-
view.selected = (view.selected + delta + n) % n;
|
|
161
|
-
}
|
|
162
|
-
return {
|
|
163
|
-
onStart(props) {
|
|
164
|
-
host = createOverlayHost();
|
|
165
|
-
document.body.append(host);
|
|
166
|
-
view.items = props.items;
|
|
167
|
-
view.selected = 0;
|
|
168
|
-
run = props.command;
|
|
169
|
-
menu = mount(SlashMenu, { target: host, props: view });
|
|
170
|
-
reposition(props.clientRect);
|
|
171
|
-
},
|
|
172
|
-
onUpdate(props) {
|
|
173
|
-
view.items = props.items;
|
|
174
|
-
// A changed query means a different result set — a stale index
|
|
175
|
-
// would point at an unrelated command.
|
|
176
|
-
view.selected = 0;
|
|
177
|
-
run = props.command;
|
|
178
|
-
reposition(props.clientRect);
|
|
179
|
-
},
|
|
180
|
-
onKeyDown(props) {
|
|
181
|
-
switch (props.event.key) {
|
|
182
|
-
case 'ArrowDown':
|
|
183
|
-
move(1);
|
|
184
|
-
return true;
|
|
185
|
-
case 'ArrowUp':
|
|
186
|
-
move(-1);
|
|
187
|
-
return true;
|
|
188
|
-
case 'Enter': {
|
|
189
|
-
const item = view.items[view.selected];
|
|
190
|
-
if (item)
|
|
191
|
-
run?.(item);
|
|
192
|
-
// Swallow Enter even with no match, so it can't split the
|
|
193
|
-
// paragraph out from under a menu that is still open.
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
case 'Escape':
|
|
197
|
-
return true;
|
|
198
|
-
default:
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
onExit() {
|
|
203
|
-
if (menu)
|
|
204
|
-
unmount(menu);
|
|
205
|
-
menu = null;
|
|
206
|
-
host?.remove();
|
|
207
|
-
host = null;
|
|
208
|
-
run = null;
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
})
|
|
213
|
-
];
|
|
214
|
-
}
|
|
215
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* The `/` command palette, rendered from era's menu vocabulary — the same
|
|
4
|
-
* floating-panel and row recipes DropdownMenu.Content / .Item use, so it picks
|
|
5
|
-
* up every axis (density tiers, surface chrome, corners, font) for free.
|
|
6
|
-
*
|
|
7
|
-
* Presentational on purpose: TipTap's Suggestion plugin owns the query,
|
|
8
|
-
* the filtered set, and the keyboard, and pushes the result in as props.
|
|
9
|
-
*/
|
|
10
|
-
import type { Component } from 'svelte';
|
|
11
|
-
import type { IconProps } from '@lucide/svelte';
|
|
12
|
-
|
|
13
|
-
interface Props {
|
|
14
|
-
items: Array<{ title: string; description: string; icon: Component<IconProps> }>;
|
|
15
|
-
/** Index of the highlighted row, driven by the plugin's key handler. */
|
|
16
|
-
selected: number;
|
|
17
|
-
onSelect: (index: number) => void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
let { items, selected, onSelect }: Props = $props();
|
|
21
|
-
|
|
22
|
-
let list = $state<HTMLDivElement | null>(null);
|
|
23
|
-
|
|
24
|
-
// Arrowing past the fold has to bring the row with it — the plugin moves the
|
|
25
|
-
// index, so nothing else would scroll the panel.
|
|
26
|
-
//
|
|
27
|
-
// Scrolled by hand rather than with scrollIntoView({block:'nearest'}): that
|
|
28
|
-
// walks EVERY scrollable ancestor, and this panel is mounted on document.body
|
|
29
|
-
// (see floating.ts), so it would scroll the page and the editor under it —
|
|
30
|
-
// once per keystroke, because a new query resets `selected` to 0. That was
|
|
31
|
-
// the jumpiness. Adjusting this element's own scrollTop cannot escape it.
|
|
32
|
-
$effect(() => {
|
|
33
|
-
// items is read so a changed result set re-runs this against the new rows.
|
|
34
|
-
void items.length;
|
|
35
|
-
const el = list;
|
|
36
|
-
const row = el?.children[selected] as HTMLElement | undefined;
|
|
37
|
-
if (!el || !row) return;
|
|
38
|
-
const top = row.offsetTop;
|
|
39
|
-
const bottom = top + row.offsetHeight;
|
|
40
|
-
if (top < el.scrollTop) el.scrollTop = top;
|
|
41
|
-
else if (bottom > el.scrollTop + el.clientHeight) el.scrollTop = bottom - el.clientHeight;
|
|
42
|
-
});
|
|
43
|
-
</script>
|
|
44
|
-
|
|
45
|
-
<!-- Rows carry shrink-0 deliberately. This is a flex COLUMN with a capped
|
|
46
|
-
height, so without it flex compresses the children to fit rather than
|
|
47
|
-
letting the panel scroll: 12 items in an 8-row box rendered every h-control row
|
|
48
|
-
at 16px against 14px text, which read as a menu with no padding at all.
|
|
49
|
-
overflow-y-auto never engaged, because the content had already been
|
|
50
|
-
squashed to fit. -->
|
|
51
|
-
<div
|
|
52
|
-
bind:this={list}
|
|
53
|
-
class="scrollbar-none flex max-h-[calc(var(--era-h-control)*8)] w-max min-w-48 flex-col overflow-y-auto rounded-control bg-elevated p-menu shadow-lg glass-blur"
|
|
54
|
-
>
|
|
55
|
-
{#if items.length === 0}
|
|
56
|
-
<div class="era-label-ink h-control shrink-0 px-inset-control text-body text-muted">
|
|
57
|
-
No commands
|
|
58
|
-
</div>
|
|
59
|
-
{:else}
|
|
60
|
-
{#each items as item, i (item.title)}
|
|
61
|
-
{@const Icon = item.icon}
|
|
62
|
-
<button
|
|
63
|
-
type="button"
|
|
64
|
-
class="flex h-control w-full shrink-0 cursor-pointer items-center gap-gutter rounded-menu-item px-inset-control text-left text-body leading-none text-fg {i ===
|
|
65
|
-
selected
|
|
66
|
-
? 'bg-highlight shadow-highlight'
|
|
67
|
-
: ''}"
|
|
68
|
-
title={item.description}
|
|
69
|
-
onmousedown={(e) => {
|
|
70
|
-
// Never let the panel take focus — the editor selection the command
|
|
71
|
-
// is about to act on would collapse before the handler runs.
|
|
72
|
-
e.preventDefault();
|
|
73
|
-
onSelect(i);
|
|
74
|
-
}}
|
|
75
|
-
>
|
|
76
|
-
<Icon class="size-icon shrink-0" />
|
|
77
|
-
<span class="flex-1">{item.title}</span>
|
|
78
|
-
<span class="shrink-0 text-muted">{item.description}</span>
|
|
79
|
-
</button>
|
|
80
|
-
{/each}
|
|
81
|
-
{/if}
|
|
82
|
-
</div>
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `/` command palette, rendered from era's menu vocabulary — the same
|
|
3
|
-
* floating-panel and row recipes DropdownMenu.Content / .Item use, so it picks
|
|
4
|
-
* up every axis (density tiers, surface chrome, corners, font) for free.
|
|
5
|
-
*
|
|
6
|
-
* Presentational on purpose: TipTap's Suggestion plugin owns the query,
|
|
7
|
-
* the filtered set, and the keyboard, and pushes the result in as props.
|
|
8
|
-
*/
|
|
9
|
-
import type { Component } from 'svelte';
|
|
10
|
-
import type { IconProps } from '@lucide/svelte';
|
|
11
|
-
interface Props {
|
|
12
|
-
items: Array<{
|
|
13
|
-
title: string;
|
|
14
|
-
description: string;
|
|
15
|
-
icon: Component<IconProps>;
|
|
16
|
-
}>;
|
|
17
|
-
/** Index of the highlighted row, driven by the plugin's key handler. */
|
|
18
|
-
selected: number;
|
|
19
|
-
onSelect: (index: number) => void;
|
|
20
|
-
}
|
|
21
|
-
declare const SlashMenu: Component<Props, {}, "">;
|
|
22
|
-
type SlashMenu = ReturnType<typeof SlashMenu>;
|
|
23
|
-
export default SlashMenu;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export { default as Notes } from './notes.svelte';
|
|
2
|
-
export { default as NoteEditor } from './note-editor.svelte';
|
|
3
|
-
export { NotesStore, localStorageAdapter, memoryAdapter, DEFAULT_STORAGE_KEY, type NotesAdapter, type NotesStoreOptions, type SaveStatus } from './notes-store.svelte.js';
|
|
4
|
-
export { noteExtensions } from './editor/extensions.js';
|
|
5
|
-
export { SlashCommand, slashItems, type SlashCommandItem } from './editor/slash-command.svelte.js';
|
|
6
|
-
export { BubbleMenu } from './editor/bubble-menu.svelte.js';
|
|
7
|
-
export { ListCleanupRule } from './editor/list-cleanup-rule.js';
|
|
8
|
-
export { toggleLink } from './editor/link.js';
|
|
9
|
-
export { tiptapToMarkdown, textContent } from './markdown.js';
|
|
10
|
-
export { buildNoteTree, buildGlobalTree, countNodes, totalWords, treeToText, extractSection, extractSectionNodes, type NoteTreeNode, type SectionResult, type SectionNodes } from './tree.js';
|
|
11
|
-
export { EMPTY_DOC, type Note, type NoteInit, type NoteListItem, type NotePatch, type TiptapDocument, type TiptapMark, type TiptapNode } from './types.js';
|
package/dist/apps/notes/index.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export { default as Notes } from './notes.svelte';
|
|
2
|
-
export { default as NoteEditor } from './note-editor.svelte';
|
|
3
|
-
export { NotesStore, localStorageAdapter, memoryAdapter, DEFAULT_STORAGE_KEY } from './notes-store.svelte.js';
|
|
4
|
-
// The editor internals, exported so a host can extend the extension set,
|
|
5
|
-
// reuse the slash items, or mount the editor without the surrounding app.
|
|
6
|
-
export { noteExtensions } from './editor/extensions.js';
|
|
7
|
-
export { SlashCommand, slashItems } from './editor/slash-command.svelte.js';
|
|
8
|
-
export { BubbleMenu } from './editor/bubble-menu.svelte.js';
|
|
9
|
-
export { ListCleanupRule } from './editor/list-cleanup-rule.js';
|
|
10
|
-
export { toggleLink } from './editor/link.js';
|
|
11
|
-
export { tiptapToMarkdown, textContent } from './markdown.js';
|
|
12
|
-
export { buildNoteTree, buildGlobalTree, countNodes, totalWords, treeToText, extractSection, extractSectionNodes } from './tree.js';
|
|
13
|
-
export { EMPTY_DOC } from './types.js';
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TipTap JSON → Markdown.
|
|
3
|
-
*
|
|
4
|
-
* A pure AST walker with no editor instance and no DOM: it runs on a stored
|
|
5
|
-
* document, not on a mounted editor. That is what lets `tree.ts` serialise one
|
|
6
|
-
* SECTION of a note (a heading and the blocks under it) — there is no editor
|
|
7
|
-
* for a slice of a document, so `editor.storage.markdown` cannot help there.
|
|
8
|
-
*/
|
|
9
|
-
import type { TiptapDocument, TiptapNode } from './types.js';
|
|
10
|
-
export declare function tiptapToMarkdown(doc: TiptapDocument): string;
|
|
11
|
-
/** Flatten a node's text, ignoring all structure. Used for `plainText` and search. */
|
|
12
|
-
export declare function textContent(node: TiptapNode): string;
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TipTap JSON → Markdown.
|
|
3
|
-
*
|
|
4
|
-
* A pure AST walker with no editor instance and no DOM: it runs on a stored
|
|
5
|
-
* document, not on a mounted editor. That is what lets `tree.ts` serialise one
|
|
6
|
-
* SECTION of a note (a heading and the blocks under it) — there is no editor
|
|
7
|
-
* for a slice of a document, so `editor.storage.markdown` cannot help there.
|
|
8
|
-
*/
|
|
9
|
-
export function tiptapToMarkdown(doc) {
|
|
10
|
-
if (!doc.content)
|
|
11
|
-
return '';
|
|
12
|
-
return doc.content.map((n) => blockToMd(n, 0)).join('\n\n');
|
|
13
|
-
}
|
|
14
|
-
/** Flatten a node's text, ignoring all structure. Used for `plainText` and search. */
|
|
15
|
-
export function textContent(node) {
|
|
16
|
-
if (node.text)
|
|
17
|
-
return node.text;
|
|
18
|
-
if (!node.content)
|
|
19
|
-
return '';
|
|
20
|
-
return node.content.map(textContent).join('');
|
|
21
|
-
}
|
|
22
|
-
// -- Block nodes ----------------------------------------------------------
|
|
23
|
-
function blockToMd(node, indent) {
|
|
24
|
-
switch (node.type) {
|
|
25
|
-
case 'heading':
|
|
26
|
-
return '#'.repeat(node.attrs?.level ?? 1) + ' ' + inline(node);
|
|
27
|
-
case 'paragraph':
|
|
28
|
-
return inline(node);
|
|
29
|
-
case 'bulletList':
|
|
30
|
-
case 'orderedList':
|
|
31
|
-
case 'taskList':
|
|
32
|
-
return listToMd(node, indent);
|
|
33
|
-
case 'codeBlock': {
|
|
34
|
-
const lang = node.attrs?.language ?? '';
|
|
35
|
-
return '```' + lang + '\n' + textContent(node) + '\n```';
|
|
36
|
-
}
|
|
37
|
-
case 'blockquote': {
|
|
38
|
-
const inner = (node.content ?? []).map((n) => blockToMd(n, 0)).join('\n\n');
|
|
39
|
-
return inner.replace(/^/gm, '> ');
|
|
40
|
-
}
|
|
41
|
-
case 'table':
|
|
42
|
-
return tableToMd(node);
|
|
43
|
-
case 'details':
|
|
44
|
-
return detailsToMd(node);
|
|
45
|
-
case 'image':
|
|
46
|
-
return ``;
|
|
47
|
-
case 'horizontalRule':
|
|
48
|
-
return '---';
|
|
49
|
-
default:
|
|
50
|
-
return textContent(node);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
// -- Lists ----------------------------------------------------------------
|
|
54
|
-
function listToMd(node, indent) {
|
|
55
|
-
const items = node.content ?? [];
|
|
56
|
-
const isOrdered = node.type === 'orderedList';
|
|
57
|
-
const isTask = node.type === 'taskList';
|
|
58
|
-
return items
|
|
59
|
-
.map((item, idx) => {
|
|
60
|
-
let marker;
|
|
61
|
-
if (isTask) {
|
|
62
|
-
marker = item.attrs?.checked ? '- [x] ' : '- [ ] ';
|
|
63
|
-
}
|
|
64
|
-
else if (isOrdered) {
|
|
65
|
-
marker = `${idx + 1}. `;
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
marker = '- ';
|
|
69
|
-
}
|
|
70
|
-
const blocks = item.content ?? [];
|
|
71
|
-
const pad = ' '.repeat(indent);
|
|
72
|
-
const continuation = pad + ' '.repeat(marker.length);
|
|
73
|
-
const lines = [];
|
|
74
|
-
for (const [i, block] of blocks.entries()) {
|
|
75
|
-
if (i === 0 && block.type === 'paragraph') {
|
|
76
|
-
lines.push(pad + marker + inline(block));
|
|
77
|
-
}
|
|
78
|
-
else if (block.type === 'bulletList' ||
|
|
79
|
-
block.type === 'orderedList' ||
|
|
80
|
-
block.type === 'taskList') {
|
|
81
|
-
lines.push(listToMd(block, indent + 1));
|
|
82
|
-
}
|
|
83
|
-
else if (block.type === 'paragraph') {
|
|
84
|
-
lines.push(continuation + inline(block));
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
lines.push(blockToMd(block, indent + 1));
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return lines.join('\n');
|
|
91
|
-
})
|
|
92
|
-
.join('\n');
|
|
93
|
-
}
|
|
94
|
-
// -- Tables ---------------------------------------------------------------
|
|
95
|
-
function tableToMd(node) {
|
|
96
|
-
const rows = (node.content ?? []).filter((r) => r.type === 'tableRow');
|
|
97
|
-
if (rows.length === 0)
|
|
98
|
-
return '';
|
|
99
|
-
const mdRows = rows.map((row) => {
|
|
100
|
-
const cells = (row.content ?? []).map((cell) => (cell.content ?? []).map((block) => inline(block)).join(' '));
|
|
101
|
-
return '| ' + cells.join(' | ') + ' |';
|
|
102
|
-
});
|
|
103
|
-
// Markdown needs the alignment row directly under the header row.
|
|
104
|
-
const colCount = (rows[0].content ?? []).length;
|
|
105
|
-
const sep = '| ' + Array.from({ length: colCount }, () => '---').join(' | ') + ' |';
|
|
106
|
-
mdRows.splice(1, 0, sep);
|
|
107
|
-
return mdRows.join('\n');
|
|
108
|
-
}
|
|
109
|
-
// -- Details/Summary ------------------------------------------------------
|
|
110
|
-
function detailsToMd(node) {
|
|
111
|
-
let summary = '';
|
|
112
|
-
const parts = [];
|
|
113
|
-
for (const child of node.content ?? []) {
|
|
114
|
-
if (child.type === 'detailsSummary') {
|
|
115
|
-
summary = inline(child);
|
|
116
|
-
}
|
|
117
|
-
else if (child.type === 'detailsContent') {
|
|
118
|
-
parts.push((child.content ?? []).map((n) => blockToMd(n, 0)).join('\n\n'));
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return `<details>\n<summary>${summary}</summary>\n\n${parts.join('\n\n')}\n</details>`;
|
|
122
|
-
}
|
|
123
|
-
// -- Inline content -------------------------------------------------------
|
|
124
|
-
function inline(node) {
|
|
125
|
-
if (!node.content)
|
|
126
|
-
return '';
|
|
127
|
-
return node.content.map(inlineNode).join('');
|
|
128
|
-
}
|
|
129
|
-
function inlineNode(node) {
|
|
130
|
-
if (node.type === 'text')
|
|
131
|
-
return applyMarks(node.text ?? '', node.marks);
|
|
132
|
-
if (node.type === 'hardBreak')
|
|
133
|
-
return '\n';
|
|
134
|
-
if (node.type === 'image')
|
|
135
|
-
return ``;
|
|
136
|
-
return textContent(node);
|
|
137
|
-
}
|
|
138
|
-
function applyMarks(text, marks) {
|
|
139
|
-
if (!marks || marks.length === 0)
|
|
140
|
-
return text;
|
|
141
|
-
// Code wins outright — emphasis inside a code span is literal backtick noise.
|
|
142
|
-
if (marks.some((m) => m.type === 'code'))
|
|
143
|
-
return `\`${text}\``;
|
|
144
|
-
let result = text;
|
|
145
|
-
for (const mark of marks) {
|
|
146
|
-
switch (mark.type) {
|
|
147
|
-
case 'bold':
|
|
148
|
-
result = `**${result}**`;
|
|
149
|
-
break;
|
|
150
|
-
case 'italic':
|
|
151
|
-
result = `*${result}*`;
|
|
152
|
-
break;
|
|
153
|
-
case 'strike':
|
|
154
|
-
result = `~~${result}~~`;
|
|
155
|
-
break;
|
|
156
|
-
case 'highlight':
|
|
157
|
-
result = `==${result}==`;
|
|
158
|
-
break;
|
|
159
|
-
case 'link':
|
|
160
|
-
result = `[${result}](${mark.attrs?.href ?? ''})`;
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return result;
|
|
165
|
-
}
|