@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,79 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* The `swarm` block: a delegated run, inline in the turn that started it.
|
|
4
|
-
*
|
|
5
|
-
* Its own component rather than a branch of llm-shell's blockView, because it
|
|
6
|
-
* needs one thing a snippet cannot hold — a $derived index of artifacts by
|
|
7
|
-
* agent. Inline, the lookup was a `{@const}` arrow re-created on every render
|
|
8
|
-
* and called once per node by the tree's expandable predicate, again by the
|
|
9
|
-
* keyboard walk, and again by the detail body: O(agents x artifacts) per event,
|
|
10
|
-
* three times over. Here it is one pass per artifact change.
|
|
11
|
-
*/
|
|
12
|
-
import * as AI from '../../ai/index.js';
|
|
13
|
-
import type { LlmSwarm } from './types.js';
|
|
14
|
-
|
|
15
|
-
let {
|
|
16
|
-
swarm,
|
|
17
|
-
view = 'tree',
|
|
18
|
-
now = Date.now(),
|
|
19
|
-
onOpenAgent,
|
|
20
|
-
onCancelAgent,
|
|
21
|
-
renderers
|
|
22
|
-
}: {
|
|
23
|
-
swarm: LlmSwarm;
|
|
24
|
-
/** `tree` keeps the delegation shape; `grid` is the console layout. */
|
|
25
|
-
view?: 'tree' | 'grid';
|
|
26
|
-
now?: number;
|
|
27
|
-
onOpenAgent?: (agent: AI.Swarm.SwarmAgent) => void;
|
|
28
|
-
onCancelAgent?: (agent: AI.Swarm.SwarmAgent) => void;
|
|
29
|
-
/** Host renderers for artifact payloads — handed to `Swarm.Root`'s registry. */
|
|
30
|
-
renderers?: AI.Swarm.SwarmContentRenderers;
|
|
31
|
-
} = $props();
|
|
32
|
-
|
|
33
|
-
// A plain record, not a Map: it is rebuilt whole by the derived, so it never
|
|
34
|
-
// needs reactivity of its own (and SvelteMap here would only add proxy cost).
|
|
35
|
-
// NONE is shared so a miss doesn't allocate a fresh array per lookup per render.
|
|
36
|
-
const NONE: AI.Swarm.SwarmArtifact[] = [];
|
|
37
|
-
const byAgent = $derived.by(() => {
|
|
38
|
-
const index: Record<string, AI.Swarm.SwarmArtifact[]> = {};
|
|
39
|
-
for (const artifact of swarm.artifacts ?? []) {
|
|
40
|
-
if (artifact.agentId == null) continue;
|
|
41
|
-
(index[artifact.agentId] ??= []).push(artifact);
|
|
42
|
-
}
|
|
43
|
-
return index;
|
|
44
|
-
});
|
|
45
|
-
const artifactsFor = (agent: AI.Swarm.SwarmAgent) => byAgent[agent.id] ?? NONE;
|
|
46
|
-
</script>
|
|
47
|
-
|
|
48
|
-
<AI.Swarm.Root {now} onOpen={onOpenAgent} onCancel={onCancelAgent} {renderers}>
|
|
49
|
-
<AI.Swarm.Header title={swarm.plan?.title} status={swarm.plan?.status}>
|
|
50
|
-
<AI.Swarm.Counts agents={swarm.agents} />
|
|
51
|
-
</AI.Swarm.Header>
|
|
52
|
-
|
|
53
|
-
{#if view === 'grid'}
|
|
54
|
-
<AI.Swarm.Grid agents={swarm.agents} />
|
|
55
|
-
<!-- The console view was dropping artifacts on the floor — cards show the
|
|
56
|
-
agents, and nothing showed what they PRODUCED. The tree hangs them off
|
|
57
|
-
each agent's detail chip; the grid gets them as one labelled section. -->
|
|
58
|
-
<AI.Swarm.Artifacts artifacts={swarm.artifacts ?? []} />
|
|
59
|
-
{:else}
|
|
60
|
-
<!-- The chip names what it opens, so the section heading inside would say it
|
|
61
|
-
twice — the detail is just the cards. -->
|
|
62
|
-
<AI.Swarm.Tree
|
|
63
|
-
agents={swarm.agents}
|
|
64
|
-
detailLabel={(agent) => {
|
|
65
|
-
const n = artifactsFor(agent).length;
|
|
66
|
-
return n === 0 ? undefined : `${n} artifact${n === 1 ? '' : 's'}`;
|
|
67
|
-
}}
|
|
68
|
-
>
|
|
69
|
-
{#snippet detail(agent)}
|
|
70
|
-
<AI.Swarm.Artifacts artifacts={artifactsFor(agent)} label={undefined} />
|
|
71
|
-
{/snippet}
|
|
72
|
-
</AI.Swarm.Tree>
|
|
73
|
-
{/if}
|
|
74
|
-
|
|
75
|
-
<!-- Fan-in gates last: they are what the branches above are waiting on. -->
|
|
76
|
-
<AI.Swarm.Barriers barriers={swarm.barriers ?? []} agents={swarm.agents} />
|
|
77
|
-
|
|
78
|
-
<AI.Swarm.Footer connection={swarm.connection} seq={swarm.seq} error={swarm.error} />
|
|
79
|
-
</AI.Swarm.Root>
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `swarm` block: a delegated run, inline in the turn that started it.
|
|
3
|
-
*
|
|
4
|
-
* Its own component rather than a branch of llm-shell's blockView, because it
|
|
5
|
-
* needs one thing a snippet cannot hold — a $derived index of artifacts by
|
|
6
|
-
* agent. Inline, the lookup was a `{@const}` arrow re-created on every render
|
|
7
|
-
* and called once per node by the tree's expandable predicate, again by the
|
|
8
|
-
* keyboard walk, and again by the detail body: O(agents x artifacts) per event,
|
|
9
|
-
* three times over. Here it is one pass per artifact change.
|
|
10
|
-
*/
|
|
11
|
-
import * as AI from '../../ai/index.js';
|
|
12
|
-
import type { LlmSwarm } from './types.js';
|
|
13
|
-
type $$ComponentProps = {
|
|
14
|
-
swarm: LlmSwarm;
|
|
15
|
-
/** `tree` keeps the delegation shape; `grid` is the console layout. */
|
|
16
|
-
view?: 'tree' | 'grid';
|
|
17
|
-
now?: number;
|
|
18
|
-
onOpenAgent?: (agent: AI.Swarm.SwarmAgent) => void;
|
|
19
|
-
onCancelAgent?: (agent: AI.Swarm.SwarmAgent) => void;
|
|
20
|
-
/** Host renderers for artifact payloads — handed to `Swarm.Root`'s registry. */
|
|
21
|
-
renderers?: AI.Swarm.SwarmContentRenderers;
|
|
22
|
-
};
|
|
23
|
-
declare const LlmSwarm: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
24
|
-
type LlmSwarm = ReturnType<typeof LlmSwarm>;
|
|
25
|
-
export default LlmSwarm;
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import type { Component } from 'svelte';
|
|
2
|
-
import type { IconProps } from '@lucide/svelte';
|
|
3
|
-
import type { ChatStatus, ConversationStatus, RunState, ToolState, UsageBreakdown } from '../../ai/index.js';
|
|
4
|
-
import type { ApprovalController } from '../../ai/confirmation/index.js';
|
|
5
|
-
import type { RuntimeFeedItemData } from '../../ai/runtime-feed/index.js';
|
|
6
|
-
import type { SwarmAgent, SwarmArtifact, SwarmBarrier, SwarmConnection, SwarmPlan } from '../../ai/swarm/index.js';
|
|
7
|
-
/** Per-turn cost footer under a finished answer. */
|
|
8
|
-
export type LlmUsage = {
|
|
9
|
-
tokensIn: number;
|
|
10
|
-
tokensOut: number;
|
|
11
|
-
model: string;
|
|
12
|
-
cost: number;
|
|
13
|
-
breakdown?: UsageBreakdown;
|
|
14
|
-
};
|
|
15
|
-
/** One tool in a compact chip row — the projectMessages "tool burst" shape. */
|
|
16
|
-
export type LlmToolChip = {
|
|
17
|
-
id: string;
|
|
18
|
-
name: string;
|
|
19
|
-
state: ToolState;
|
|
20
|
-
icon?: Component<IconProps>;
|
|
21
|
-
input?: unknown;
|
|
22
|
-
output?: unknown;
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* Where a swarm block reads its state.
|
|
26
|
-
*
|
|
27
|
-
* Deliberately structural rather than a class: a `SwarmController` satisfies it
|
|
28
|
-
* as-is (so a live, streaming swarm is `{ kind: 'swarm', swarm: controller }`
|
|
29
|
-
* and stays reactive), and a host with static state passes a plain object with
|
|
30
|
-
* the same fields. Neither has to know about the other.
|
|
31
|
-
*/
|
|
32
|
-
export type LlmSwarm = {
|
|
33
|
-
agents: SwarmAgent[];
|
|
34
|
-
plan?: SwarmPlan | null;
|
|
35
|
-
barriers?: SwarmBarrier[];
|
|
36
|
-
artifacts?: SwarmArtifact[];
|
|
37
|
-
connection?: SwarmConnection;
|
|
38
|
-
seq?: number;
|
|
39
|
-
/** Last transport error. A `SwarmController` exposes this under the same name. */
|
|
40
|
-
error?: string | null;
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* Everything an assistant turn can contain.
|
|
44
|
-
*
|
|
45
|
-
* The union is the app's contract: it covers every presentation the AI suite
|
|
46
|
-
* has, so a host maps its own stream into these and never renders AI parts by
|
|
47
|
-
* hand. `tool-row` is the compact chips view (one shared expansion area);
|
|
48
|
-
* `tool` is the full expanded step with input, trace and output.
|
|
49
|
-
*/
|
|
50
|
-
export type LlmBlock = {
|
|
51
|
-
kind: 'reasoning';
|
|
52
|
-
text: string;
|
|
53
|
-
duration: number;
|
|
54
|
-
streaming: boolean;
|
|
55
|
-
} | {
|
|
56
|
-
kind: 'tool';
|
|
57
|
-
name: string;
|
|
58
|
-
input: unknown;
|
|
59
|
-
output?: unknown;
|
|
60
|
-
state: ToolState;
|
|
61
|
-
feed?: RuntimeFeedItemData[];
|
|
62
|
-
} | {
|
|
63
|
-
kind: 'tool-row';
|
|
64
|
-
tools: LlmToolChip[];
|
|
65
|
-
} | {
|
|
66
|
-
kind: 'exec';
|
|
67
|
-
command: string;
|
|
68
|
-
stdout?: string;
|
|
69
|
-
stderr?: string;
|
|
70
|
-
exitCode?: number;
|
|
71
|
-
} | {
|
|
72
|
-
kind: 'swarm';
|
|
73
|
-
swarm: LlmSwarm;
|
|
74
|
-
/** `tree` (default) keeps the delegation shape; `grid` is the console view. */
|
|
75
|
-
view?: 'tree' | 'grid';
|
|
76
|
-
} | {
|
|
77
|
-
kind: 'approval';
|
|
78
|
-
toolName: string;
|
|
79
|
-
controller: ApprovalController;
|
|
80
|
-
} | {
|
|
81
|
-
kind: 'error';
|
|
82
|
-
error: unknown;
|
|
83
|
-
} | {
|
|
84
|
-
kind: 'answer';
|
|
85
|
-
text: string;
|
|
86
|
-
streaming: boolean;
|
|
87
|
-
usage?: LlmUsage;
|
|
88
|
-
};
|
|
89
|
-
export type LlmMessage = {
|
|
90
|
-
id: number | string;
|
|
91
|
-
role: 'user' | 'assistant';
|
|
92
|
-
text?: string;
|
|
93
|
-
blocks?: LlmBlock[];
|
|
94
|
-
};
|
|
95
|
-
export type LlmConversation = {
|
|
96
|
-
id: string;
|
|
97
|
-
title: string;
|
|
98
|
-
updatedAt: number;
|
|
99
|
-
starred?: boolean;
|
|
100
|
-
status?: ConversationStatus;
|
|
101
|
-
messages: LlmMessage[];
|
|
102
|
-
};
|
|
103
|
-
export type { ChatStatus, RunState };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The selection ("bubble") toolbar.
|
|
3
|
-
*
|
|
4
|
-
* A plain ProseMirror plugin view rather than `@tiptap/extension-bubble-menu`:
|
|
5
|
-
* the official extension brings its own positioning engine and its own DOM, and
|
|
6
|
-
* both would have to be re-skinned anyway. This mounts an era `Bar` of `Button`s
|
|
7
|
-
* with `mount()` and drives it from `update()` — the same approach the slash
|
|
8
|
-
* menu takes, so both overlays share one placement helper and one set of tokens.
|
|
9
|
-
*/
|
|
10
|
-
import { Extension } from '@tiptap/core';
|
|
11
|
-
export declare const BubbleMenu: Extension<any, any>;
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The selection ("bubble") toolbar.
|
|
3
|
-
*
|
|
4
|
-
* A plain ProseMirror plugin view rather than `@tiptap/extension-bubble-menu`:
|
|
5
|
-
* the official extension brings its own positioning engine and its own DOM, and
|
|
6
|
-
* both would have to be re-skinned anyway. This mounts an era `Bar` of `Button`s
|
|
7
|
-
* with `mount()` and drives it from `update()` — the same approach the slash
|
|
8
|
-
* menu takes, so both overlays share one placement helper and one set of tokens.
|
|
9
|
-
*/
|
|
10
|
-
import { Extension } from '@tiptap/core';
|
|
11
|
-
import { Plugin, PluginKey } from '@tiptap/pm/state';
|
|
12
|
-
import { flushSync, mount, unmount } from 'svelte';
|
|
13
|
-
import Bold from '@lucide/svelte/icons/bold';
|
|
14
|
-
import Italic from '@lucide/svelte/icons/italic';
|
|
15
|
-
import Strikethrough from '@lucide/svelte/icons/strikethrough';
|
|
16
|
-
import Code from '@lucide/svelte/icons/code';
|
|
17
|
-
import Highlighter from '@lucide/svelte/icons/highlighter';
|
|
18
|
-
import LinkIcon from '@lucide/svelte/icons/link';
|
|
19
|
-
import BubbleToolbar from './bubble-toolbar.svelte';
|
|
20
|
-
import { createOverlayHost, placeOverSelection } from './floating.js';
|
|
21
|
-
import { toggleLink } from './link.js';
|
|
22
|
-
const buttons = [
|
|
23
|
-
{
|
|
24
|
-
mark: 'bold',
|
|
25
|
-
label: 'Bold',
|
|
26
|
-
icon: Bold,
|
|
27
|
-
command: (ed) => ed.chain().focus().toggleBold().run()
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
mark: 'italic',
|
|
31
|
-
label: 'Italic',
|
|
32
|
-
icon: Italic,
|
|
33
|
-
command: (ed) => ed.chain().focus().toggleItalic().run()
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
mark: 'strike',
|
|
37
|
-
label: 'Strikethrough',
|
|
38
|
-
icon: Strikethrough,
|
|
39
|
-
command: (ed) => ed.chain().focus().toggleStrike().run()
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
mark: 'code',
|
|
43
|
-
label: 'Code',
|
|
44
|
-
icon: Code,
|
|
45
|
-
command: (ed) => ed.chain().focus().toggleCode().run()
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
mark: 'highlight',
|
|
49
|
-
label: 'Highlight',
|
|
50
|
-
icon: Highlighter,
|
|
51
|
-
command: (ed) => ed.chain().focus().toggleHighlight().run()
|
|
52
|
-
},
|
|
53
|
-
{ mark: 'link', label: 'Link', icon: LinkIcon, command: toggleLink }
|
|
54
|
-
];
|
|
55
|
-
const items = buttons.map(({ mark, label, icon }) => ({ mark, label, icon }));
|
|
56
|
-
class BubbleMenuView {
|
|
57
|
-
#host;
|
|
58
|
-
#toolbar;
|
|
59
|
-
#editor;
|
|
60
|
-
/**
|
|
61
|
-
* The DOCUMENT range the toolbar is currently parked over, or null while it is
|
|
62
|
-
* hidden. This is what the toolbar is anchored to — see update().
|
|
63
|
-
*/
|
|
64
|
-
#anchor = null;
|
|
65
|
-
#view = $state({
|
|
66
|
-
items,
|
|
67
|
-
active: {},
|
|
68
|
-
onCommand: (_mark) => { }
|
|
69
|
-
});
|
|
70
|
-
constructor(editor) {
|
|
71
|
-
this.#editor = editor;
|
|
72
|
-
this.#view.onCommand = (mark) => buttons.find((b) => b.mark === mark)?.command(editor);
|
|
73
|
-
this.#host = createOverlayHost();
|
|
74
|
-
this.#host.style.display = 'none';
|
|
75
|
-
document.body.append(this.#host);
|
|
76
|
-
this.#toolbar = mount(BubbleToolbar, { target: this.#host, props: this.#view });
|
|
77
|
-
}
|
|
78
|
-
update(view) {
|
|
79
|
-
const { state } = view;
|
|
80
|
-
const { selection } = state;
|
|
81
|
-
// Hide for a caret, an unfocused editor, a selection with no text (an image
|
|
82
|
-
// or table node), and inside a code block where marks do not apply.
|
|
83
|
-
if (selection.empty ||
|
|
84
|
-
!view.hasFocus() ||
|
|
85
|
-
!state.doc.textBetween(selection.from, selection.to).length ||
|
|
86
|
-
this.#editor.isActive('codeBlock')) {
|
|
87
|
-
this.#host.style.display = 'none';
|
|
88
|
-
this.#anchor = null;
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
this.#view.active = Object.fromEntries(buttons.map((b) => [b.mark, this.#editor.isActive(b.mark)]));
|
|
92
|
-
const wasHidden = this.#host.style.display === 'none';
|
|
93
|
-
this.#host.style.display = 'block';
|
|
94
|
-
/*
|
|
95
|
-
* The toolbar is anchored to the SELECTION RANGE, not to its pixel box.
|
|
96
|
-
*
|
|
97
|
-
* Those are not the same thing, and the difference is the whole bug:
|
|
98
|
-
* applying a mark re-renders the selected text — code sets it monospace, a
|
|
99
|
-
* link adds an underline — so its rect moves while the selection itself
|
|
100
|
-
* does not change by a single character. Re-centring on the new rect slides
|
|
101
|
-
* the toolbar out from under the pointer that just clicked it, and the next
|
|
102
|
-
* click lands on nothing. The user has to chase the menu to keep using it.
|
|
103
|
-
*
|
|
104
|
-
* So: reposition only when the range actually changes (or when the toolbar
|
|
105
|
-
* is appearing fresh). While `from`/`to` hold steady the toolbar is pinned,
|
|
106
|
-
* and a whole run of formatting happens under a motionless pointer.
|
|
107
|
-
*
|
|
108
|
-
* The trade is deliberate: if a format reflows the text far enough, the
|
|
109
|
-
* toolbar can end up slightly off its selection until the selection next
|
|
110
|
-
* changes. Being a few pixels stale beats moving while it is being used —
|
|
111
|
-
* a toolbar is a target first and an indicator second.
|
|
112
|
-
*/
|
|
113
|
-
const { from, to } = selection;
|
|
114
|
-
const movedRange = !this.#anchor || this.#anchor.from !== from || this.#anchor.to !== to;
|
|
115
|
-
if (wasHidden || movedRange) {
|
|
116
|
-
// flushSync applies the `active` change above BEFORE measuring — reading
|
|
117
|
-
// a Svelte component's box before its pending render lands gives the
|
|
118
|
-
// previous frame's, the same trap the slash menu documents. Only needed
|
|
119
|
-
// on the frames that actually measure.
|
|
120
|
-
flushSync();
|
|
121
|
-
placeOverSelection(this.#host, selectionRect(view));
|
|
122
|
-
this.#anchor = { from, to };
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
destroy() {
|
|
126
|
-
unmount(this.#toolbar);
|
|
127
|
-
this.#host.remove();
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* The selection's bounding box in viewport coordinates.
|
|
132
|
-
*
|
|
133
|
-
* Prefers the live DOM selection, whose rect already unions every line of a
|
|
134
|
-
* multi-line selection. ProseMirror's coordsAtPos only describes ONE position,
|
|
135
|
-
* so reconstructing a tall selection's box from its two endpoints is what put
|
|
136
|
-
* the toolbar 305px off centre. The endpoint union is kept as a fallback for the
|
|
137
|
-
* case where the DOM selection is unavailable or collapsed to a zero rect (it
|
|
138
|
-
* still beats nothing, and it is exactly right on a single line).
|
|
139
|
-
*/
|
|
140
|
-
function selectionRect(view) {
|
|
141
|
-
const dom = window.getSelection();
|
|
142
|
-
if (dom && dom.rangeCount > 0) {
|
|
143
|
-
const rect = dom.getRangeAt(0).getBoundingClientRect();
|
|
144
|
-
if (rect.width > 0 || rect.height > 0)
|
|
145
|
-
return rect;
|
|
146
|
-
}
|
|
147
|
-
const { from, to } = view.state.selection;
|
|
148
|
-
const a = view.coordsAtPos(from);
|
|
149
|
-
const b = view.coordsAtPos(to);
|
|
150
|
-
const left = Math.min(a.left, b.left);
|
|
151
|
-
const top = Math.min(a.top, b.top);
|
|
152
|
-
return new DOMRect(left, top, Math.max(a.right, b.right) - left, Math.max(a.bottom, b.bottom) - top);
|
|
153
|
-
}
|
|
154
|
-
export const BubbleMenu = Extension.create({
|
|
155
|
-
name: 'notesBubbleMenu',
|
|
156
|
-
addProseMirrorPlugins() {
|
|
157
|
-
const editor = this.editor;
|
|
158
|
-
return [
|
|
159
|
-
new Plugin({
|
|
160
|
-
key: new PluginKey('notesBubbleMenu'),
|
|
161
|
-
view: () => new BubbleMenuView(editor)
|
|
162
|
-
})
|
|
163
|
-
];
|
|
164
|
-
}
|
|
165
|
-
});
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* The selection toolbar — an era `Bar` of latched icon `Button`s.
|
|
4
|
-
*
|
|
5
|
-
* `active` is Button's latched state (it emits `data-state="on"`), so a mark
|
|
6
|
-
* that is on reads as a held-down key on every surface, including bevel where
|
|
7
|
-
* the whole affordance is depth rather than tone.
|
|
8
|
-
*/
|
|
9
|
-
import type { Component } from 'svelte';
|
|
10
|
-
import type { IconProps } from '@lucide/svelte';
|
|
11
|
-
import { Bar } from '../../../ui/bar/index.js';
|
|
12
|
-
import { Button } from '../../../ui/button/index.js';
|
|
13
|
-
|
|
14
|
-
interface Props {
|
|
15
|
-
items: Array<{ mark: string; label: string; icon: Component<IconProps> }>;
|
|
16
|
-
/** Which marks are active at the current selection. */
|
|
17
|
-
active: Record<string, boolean>;
|
|
18
|
-
onCommand: (mark: string) => void;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
let { items, active, onCommand }: Props = $props();
|
|
22
|
-
</script>
|
|
23
|
-
|
|
24
|
-
<!-- md with xxs buttons, matching the app's other bars. It was sm + size="icon"
|
|
25
|
-
— a 22px bar of 14px buttons around 10px glyphs, the smallest combination in
|
|
26
|
-
the ladder, which is why the formatting controls read as unhittable.
|
|
27
|
-
|
|
28
|
-
The border is a deliberate exception to "shadow, not border" (design rule 7):
|
|
29
|
-
this panel floats over the DOCUMENT, not over chrome, so on a light theme its
|
|
30
|
-
elevated fill sits on a page of nearly the same value and the shadow alone
|
|
31
|
-
leaves no discernible edge. border-divider-faded is the token'd hairline, so
|
|
32
|
-
it still tracks the surface rather than being a drawn-on line. -->
|
|
33
|
-
<Bar
|
|
34
|
-
size="control"
|
|
35
|
-
content="pill"
|
|
36
|
-
class="w-max border border-divider-faded bg-elevated shadow-lg glass-blur"
|
|
37
|
-
>
|
|
38
|
-
{#each items as item (item.mark)}
|
|
39
|
-
{@const Icon = item.icon}
|
|
40
|
-
<Button
|
|
41
|
-
icon
|
|
42
|
-
size="pill"
|
|
43
|
-
active={active[item.mark] ?? false}
|
|
44
|
-
aria-label={item.label}
|
|
45
|
-
title={item.label}
|
|
46
|
-
onmousedown={(e: MouseEvent) => {
|
|
47
|
-
// Taking focus would collapse the very selection the command formats.
|
|
48
|
-
e.preventDefault();
|
|
49
|
-
onCommand(item.mark);
|
|
50
|
-
}}
|
|
51
|
-
>
|
|
52
|
-
<Icon />
|
|
53
|
-
</Button>
|
|
54
|
-
{/each}
|
|
55
|
-
</Bar>
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The selection toolbar — an era `Bar` of latched icon `Button`s.
|
|
3
|
-
*
|
|
4
|
-
* `active` is Button's latched state (it emits `data-state="on"`), so a mark
|
|
5
|
-
* that is on reads as a held-down key on every surface, including bevel where
|
|
6
|
-
* the whole affordance is depth rather than tone.
|
|
7
|
-
*/
|
|
8
|
-
import type { Component } from 'svelte';
|
|
9
|
-
import type { IconProps } from '@lucide/svelte';
|
|
10
|
-
interface Props {
|
|
11
|
-
items: Array<{
|
|
12
|
-
mark: string;
|
|
13
|
-
label: string;
|
|
14
|
-
icon: Component<IconProps>;
|
|
15
|
-
}>;
|
|
16
|
-
/** Which marks are active at the current selection. */
|
|
17
|
-
active: Record<string, boolean>;
|
|
18
|
-
onCommand: (mark: string) => void;
|
|
19
|
-
}
|
|
20
|
-
declare const BubbleToolbar: Component<Props, {}, "">;
|
|
21
|
-
type BubbleToolbar = ReturnType<typeof BubbleToolbar>;
|
|
22
|
-
export default BubbleToolbar;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Notes editor's extension set.
|
|
3
|
-
*
|
|
4
|
-
* Ported from the term/web notes app with two deliberate changes:
|
|
5
|
-
*
|
|
6
|
-
* - The slash menu and bubble toolbar are era components (see the sibling
|
|
7
|
-
* `.svelte.ts` files) instead of hand-built DOM, so the editor chrome tracks
|
|
8
|
-
* the density / surface / corners / motion / font axes like the rest of the
|
|
9
|
-
* library.
|
|
10
|
-
* - `@tiptap/extension-mathematics` is NOT included. It needs KaTeX's stylesheet
|
|
11
|
-
* and font files to render anything but raw markup, and that is a large,
|
|
12
|
-
* single-feature dependency to force on every consumer of this subpath. Add it
|
|
13
|
-
* in your own extension list (and import `katex/dist/katex.min.css`) if you
|
|
14
|
-
* want it — `noteExtensions` is a plain array, so spreading and extending it
|
|
15
|
-
* is the supported path.
|
|
16
|
-
*/
|
|
17
|
-
import { type Extensions } from '@tiptap/core';
|
|
18
|
-
export declare const noteExtensions: Extensions;
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Notes editor's extension set.
|
|
3
|
-
*
|
|
4
|
-
* Ported from the term/web notes app with two deliberate changes:
|
|
5
|
-
*
|
|
6
|
-
* - The slash menu and bubble toolbar are era components (see the sibling
|
|
7
|
-
* `.svelte.ts` files) instead of hand-built DOM, so the editor chrome tracks
|
|
8
|
-
* the density / surface / corners / motion / font axes like the rest of the
|
|
9
|
-
* library.
|
|
10
|
-
* - `@tiptap/extension-mathematics` is NOT included. It needs KaTeX's stylesheet
|
|
11
|
-
* and font files to render anything but raw markup, and that is a large,
|
|
12
|
-
* single-feature dependency to force on every consumer of this subpath. Add it
|
|
13
|
-
* in your own extension list (and import `katex/dist/katex.min.css`) if you
|
|
14
|
-
* want it — `noteExtensions` is a plain array, so spreading and extending it
|
|
15
|
-
* is the supported path.
|
|
16
|
-
*/
|
|
17
|
-
import { Extension } from '@tiptap/core';
|
|
18
|
-
import StarterKit from '@tiptap/starter-kit';
|
|
19
|
-
import { TaskList } from '@tiptap/extension-task-list';
|
|
20
|
-
import { TaskItem } from '@tiptap/extension-task-item';
|
|
21
|
-
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
|
|
22
|
-
import Placeholder from '@tiptap/extension-placeholder';
|
|
23
|
-
import Typography from '@tiptap/extension-typography';
|
|
24
|
-
import { Table, TableRow, TableCell, TableHeader } from '@tiptap/extension-table';
|
|
25
|
-
import { Highlight } from '@tiptap/extension-highlight';
|
|
26
|
-
import { Details, DetailsSummary, DetailsContent } from '@tiptap/extension-details';
|
|
27
|
-
import Image from '@tiptap/extension-image';
|
|
28
|
-
import { UniqueID } from '@tiptap/extension-unique-id';
|
|
29
|
-
import { Markdown } from 'tiptap-markdown';
|
|
30
|
-
import { common, createLowlight } from 'lowlight';
|
|
31
|
-
import { ListCleanupRule } from './list-cleanup-rule.js';
|
|
32
|
-
import { SlashCommand } from './slash-command.svelte.js';
|
|
33
|
-
import { BubbleMenu } from './bubble-menu.svelte.js';
|
|
34
|
-
import { toggleLink } from './link.js';
|
|
35
|
-
const LinkShortcut = Extension.create({
|
|
36
|
-
name: 'linkShortcut',
|
|
37
|
-
addKeyboardShortcuts() {
|
|
38
|
-
return {
|
|
39
|
-
'Mod-k': ({ editor }) => {
|
|
40
|
-
toggleLink(editor);
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
const lowlight = createLowlight(common);
|
|
47
|
-
export const noteExtensions = [
|
|
48
|
-
StarterKit.configure({
|
|
49
|
-
heading: { levels: [1, 2, 3] },
|
|
50
|
-
// Replaced by the lowlight variant below.
|
|
51
|
-
codeBlock: false,
|
|
52
|
-
dropcursor: { color: 'var(--color-8)', width: 2 }
|
|
53
|
-
}),
|
|
54
|
-
CodeBlockLowlight.configure({ lowlight }),
|
|
55
|
-
TaskList.configure({
|
|
56
|
-
HTMLAttributes: { class: 'task-list' }
|
|
57
|
-
}),
|
|
58
|
-
TaskItem.configure({
|
|
59
|
-
nested: true,
|
|
60
|
-
HTMLAttributes: { class: 'task-item' },
|
|
61
|
-
a11y: {
|
|
62
|
-
checkboxLabel: (_node, checked) => (checked ? 'Task completed' : 'Task not completed')
|
|
63
|
-
}
|
|
64
|
-
}),
|
|
65
|
-
// Tables
|
|
66
|
-
Table.configure({ resizable: false, HTMLAttributes: { class: 'note-table' } }),
|
|
67
|
-
TableRow,
|
|
68
|
-
TableCell,
|
|
69
|
-
TableHeader,
|
|
70
|
-
// Inline formatting
|
|
71
|
-
Highlight.configure({ multicolor: false }),
|
|
72
|
-
// Block types
|
|
73
|
-
Details,
|
|
74
|
-
DetailsSummary,
|
|
75
|
-
DetailsContent,
|
|
76
|
-
Image.configure({ inline: true, allowBase64: true }),
|
|
77
|
-
// Utility
|
|
78
|
-
Placeholder.configure({ placeholder: 'Type / for commands…' }),
|
|
79
|
-
Typography,
|
|
80
|
-
// Stable ids on headings are what `tree.ts` addresses sections by, so
|
|
81
|
-
// section retrieval survives edits above the heading.
|
|
82
|
-
UniqueID.configure({ types: ['heading', 'paragraph', 'listItem', 'taskItem'] }),
|
|
83
|
-
Markdown.configure({ html: false }),
|
|
84
|
-
ListCleanupRule,
|
|
85
|
-
SlashCommand,
|
|
86
|
-
LinkShortcut,
|
|
87
|
-
BubbleMenu
|
|
88
|
-
];
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Placement for the two editor overlays (slash menu, bubble toolbar).
|
|
3
|
-
*
|
|
4
|
-
* They are mounted at the document root rather than inside the editor so a
|
|
5
|
-
* scrolling or `overflow: hidden` editor pane can never clip them — which means
|
|
6
|
-
* they are positioned in viewport coordinates and have to keep themselves on
|
|
7
|
-
* screen. ProseMirror hands out viewport rects already, so `position: fixed` is
|
|
8
|
-
* the natural fit and no scroll-offset bookkeeping is needed.
|
|
9
|
-
*/
|
|
10
|
-
/** Create the fixed, root-level host element an overlay component mounts into. */
|
|
11
|
-
export declare function createOverlayHost(): HTMLDivElement;
|
|
12
|
-
/** Anchor below `rect`, flipping above when the bottom edge would run off. */
|
|
13
|
-
export declare function placeBelow(host: HTMLElement, rect: DOMRect, gap?: number): void;
|
|
14
|
-
/**
|
|
15
|
-
* Centre over a selection's BOUNDING BOX, flipping below when there is no room
|
|
16
|
-
* above.
|
|
17
|
-
*
|
|
18
|
-
* Takes the whole rect on purpose. This used to take the two endpoint coords and
|
|
19
|
-
* centre on `(start.left + end.right) / 2`, which is only correct while the
|
|
20
|
-
* selection sits on one line: across several lines it averages an x from the
|
|
21
|
-
* FIRST line with an x from the LAST, and the result points at neither. Measured
|
|
22
|
-
* on a 7-line selection, the toolbar landed 305px from the selection's centre —
|
|
23
|
-
* one line was 0px, two lines 4px, so it looked fine until a selection got tall.
|
|
24
|
-
*/
|
|
25
|
-
export declare function placeOverSelection(host: HTMLElement, rect: DOMRect, gap?: number): void;
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Placement for the two editor overlays (slash menu, bubble toolbar).
|
|
3
|
-
*
|
|
4
|
-
* They are mounted at the document root rather than inside the editor so a
|
|
5
|
-
* scrolling or `overflow: hidden` editor pane can never clip them — which means
|
|
6
|
-
* they are positioned in viewport coordinates and have to keep themselves on
|
|
7
|
-
* screen. ProseMirror hands out viewport rects already, so `position: fixed` is
|
|
8
|
-
* the natural fit and no scroll-offset bookkeeping is needed.
|
|
9
|
-
*/
|
|
10
|
-
const EDGE = 4;
|
|
11
|
-
/** Create the fixed, root-level host element an overlay component mounts into. */
|
|
12
|
-
export function createOverlayHost() {
|
|
13
|
-
const host = document.createElement('div');
|
|
14
|
-
// z-50 is the bits-ui portal band — the same layer menus and dialogs use, so
|
|
15
|
-
// editor chrome stacks consistently with the rest of the library.
|
|
16
|
-
host.style.cssText = 'position:fixed;top:0;left:0;z-index:50;';
|
|
17
|
-
return host;
|
|
18
|
-
}
|
|
19
|
-
/** Anchor below `rect`, flipping above when the bottom edge would run off. */
|
|
20
|
-
export function placeBelow(host, rect, gap = EDGE) {
|
|
21
|
-
const box = host.getBoundingClientRect();
|
|
22
|
-
const left = clamp(rect.left, EDGE, window.innerWidth - box.width - EDGE);
|
|
23
|
-
let top = rect.bottom + gap;
|
|
24
|
-
if (top + box.height > window.innerHeight - EDGE) {
|
|
25
|
-
const above = rect.top - box.height - gap;
|
|
26
|
-
top = above >= EDGE ? above : window.innerHeight - box.height - EDGE;
|
|
27
|
-
}
|
|
28
|
-
host.style.left = `${Math.max(EDGE, left)}px`;
|
|
29
|
-
host.style.top = `${Math.max(EDGE, top)}px`;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Centre over a selection's BOUNDING BOX, flipping below when there is no room
|
|
33
|
-
* above.
|
|
34
|
-
*
|
|
35
|
-
* Takes the whole rect on purpose. This used to take the two endpoint coords and
|
|
36
|
-
* centre on `(start.left + end.right) / 2`, which is only correct while the
|
|
37
|
-
* selection sits on one line: across several lines it averages an x from the
|
|
38
|
-
* FIRST line with an x from the LAST, and the result points at neither. Measured
|
|
39
|
-
* on a 7-line selection, the toolbar landed 305px from the selection's centre —
|
|
40
|
-
* one line was 0px, two lines 4px, so it looked fine until a selection got tall.
|
|
41
|
-
*/
|
|
42
|
-
export function placeOverSelection(host, rect, gap = 6) {
|
|
43
|
-
const box = host.getBoundingClientRect();
|
|
44
|
-
const centerX = rect.left + rect.width / 2;
|
|
45
|
-
const left = clamp(centerX - box.width / 2, EDGE, window.innerWidth - box.width - EDGE);
|
|
46
|
-
let top = rect.top - box.height - gap;
|
|
47
|
-
if (top < EDGE)
|
|
48
|
-
top = rect.bottom + gap;
|
|
49
|
-
host.style.left = `${Math.max(EDGE, left)}px`;
|
|
50
|
-
host.style.top = `${Math.max(EDGE, top)}px`;
|
|
51
|
-
}
|
|
52
|
-
function clamp(value, min, max) {
|
|
53
|
-
return Math.min(Math.max(value, min), Math.max(min, max));
|
|
54
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { Editor } from '@tiptap/core';
|
|
2
|
-
/**
|
|
3
|
-
* The one link behaviour, shared by the bubble toolbar and Mod-K.
|
|
4
|
-
*
|
|
5
|
-
* Three cases, in order: an existing link is removed; a selection that is
|
|
6
|
-
* already a bare URL links to itself (the common paste-then-select gesture);
|
|
7
|
-
* anything else asks for a target.
|
|
8
|
-
*/
|
|
9
|
-
export declare function toggleLink(editor: Editor): void;
|