create-pmos 0.106.0 → 0.108.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/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/guide/notes.md +7 -5
- package/workspace/lib/notes.js +98 -1
- package/workspace/lib/routes-notes.js +32 -11
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/app.js +2 -2
- package/workspace/public/pm/components.css +315 -163
- package/workspace/public/pm/dest-notes.js +383 -0
- package/workspace/public/pm/dom.js +70 -3
- package/workspace/public/pm/frame-copy.js +1 -0
- package/workspace/public/pm/note-dock.js +78 -22
- package/workspace/public/pm/notes-links.js +3 -3
- package/workspace/public/pm/notes-task.js +36 -0
- package/workspace/public/pm/panel-cockpit.js +1 -2
- package/workspace/public/pm/registry.js +2 -0
- package/workspace/public/pm/topbar.js +42 -17
- package/workspace/public/pm/view-notes.js +135 -165
- package/workspace/public/vendor/lucide-sprite.js +8 -2
- package/workspace/test-files.json +3 -1
- package/workspace/test-lists/meeting-notes.json +5 -3
- package/workspace/third-party/README.md +1 -1
- package/workspace/public/pm/view-notes-menu.js +0 -317
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* pm/notes-task.js — Make task: a meeting note's next step as a plan item of the note's initiative (meeting-notes Run 12,
|
|
2
|
+
* mockup board 3 "Next step to task in one click").
|
|
3
|
+
*
|
|
4
|
+
* makeTask(note, { text, owner?, due? }, { repo?, btn? }) → Promise<item id | null>
|
|
5
|
+
*
|
|
6
|
+
* One press, no form: the item goes into the initiative's first stage through the Breakdown's own writer (POST
|
|
7
|
+
* /api/plan-items, as the PM), with the owner when the line names one and the due day when it is a date. A toast says
|
|
8
|
+
* where it went and offers Undo (the item is withdrawn). A note with no initiative gets a toast that says to link one.
|
|
9
|
+
*/
|
|
10
|
+
import { toast, SEL } from './primitives.js';
|
|
11
|
+
import { pmGet, pmPost } from './http.js';
|
|
12
|
+
import { wsOf } from './data.js';
|
|
13
|
+
|
|
14
|
+
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
15
|
+
const iniName = (i) => (i ? i.title || i.id : null);
|
|
16
|
+
const label = (btn, text, done) => { if (!btn) return; const l = btn.querySelector(SEL.btnLabel); if (l) l.textContent = text; btn.disabled = !!done; };
|
|
17
|
+
|
|
18
|
+
export async function makeTask(n, x, { repo, btn } = {}) {
|
|
19
|
+
const ini = n.initiative;
|
|
20
|
+
const ws = ini && (ini.ws || n.ws || (repo ? wsOf(repo) : null));
|
|
21
|
+
if (!ini || !ini.id || !ws) { toast({ text: 'Link the note to an initiative first. Make task adds the step to its breakdown.' }); return null; }
|
|
22
|
+
if (btn) btn.disabled = true;
|
|
23
|
+
const lay = await pmGet('/api/plan-items?ladder=0&ws=' + encodeURIComponent(ws) + '&id=' + encodeURIComponent(ini.id));
|
|
24
|
+
const stage = lay && lay.ok && (lay.stages || [])[0];
|
|
25
|
+
if (!stage) { if (btn) btn.disabled = false; toast({ text: 'The task was not made: ' + ((lay && lay.error) || 'the initiative has no stage'), tone: 'destructive' }); return null; }
|
|
26
|
+
const ev = { ev: 'planned', initiative: ini.id, title: String(x.text || '').slice(0, 200), stage: stage.id, note: 'From the meeting note "' + (n.title || 'Untitled note') + '"' };
|
|
27
|
+
if (x.owner) ev.owner = /^(you|me)$/i.test(x.owner) ? 'you' : 'person:' + String(x.owner).slice(0, 60);
|
|
28
|
+
if (x.due && DATE_RE.test(x.due)) ev.due = x.due;
|
|
29
|
+
const r = await pmPost('/api/plan-items', { ws, events: [ev] });
|
|
30
|
+
if (!r || !r.ok) { if (btn) btn.disabled = false; toast({ text: 'The task was not made' + ((r && r.error) ? ': ' + r.error : ''), tone: 'destructive' }); return null; }
|
|
31
|
+
const item = (r.written && r.written[0] && r.written[0].item) || null;
|
|
32
|
+
label(btn, 'Task made', true);
|
|
33
|
+
toast({ text: 'Added to ' + iniName(ini) + ', under ' + (stage.label || stage.id), actionLabel: item ? 'Undo' : undefined,
|
|
34
|
+
onAction: item ? async () => { const u = await pmPost('/api/plan-items', { ws, events: [{ ev: 'withdrawn', item, initiative: ini.id }] }); if (u && u.ok) label(btn, 'Make task', false); } : undefined });
|
|
35
|
+
return item;
|
|
36
|
+
}
|
|
@@ -47,7 +47,6 @@ export const VIEWS = {
|
|
|
47
47
|
// meeting-notes Run 1: typed notes, dictation, meeting transcripts and summaries; the PM's own, the same in every repository
|
|
48
48
|
notes: { label: 'notes', mode: null, scoped: false, palette: true, hint: 'type, dictate or record a meeting' },
|
|
49
49
|
// meeting-notes Run 11: notes menu with two-column view grouped by initiative (R47)
|
|
50
|
-
notesmenu: { label: 'notes menu', mode: null, scoped: false, hint: 'view and organize all notes by initiative' },
|
|
51
50
|
};
|
|
52
51
|
|
|
53
52
|
/** The view a list mode opens as, or null when the mode is not a view. `product` is the list itself. */
|
|
@@ -97,7 +96,7 @@ function modes() {
|
|
|
97
96
|
* every `data-*` hook the suites use, and only adds the board's frame around it. A view with no board module draws as before. */
|
|
98
97
|
export const BOARD_MODULES = {
|
|
99
98
|
today: './view-today.js', validate: './view-validate.js', addrepo: './view-addrepo.js', livenotes: './view-livenotes.js',
|
|
100
|
-
notes: './view-notes.js',
|
|
99
|
+
notes: './view-notes.js',
|
|
101
100
|
};
|
|
102
101
|
const boardMods = {}; // view -> undefined (not tried) · null (loading or absent) · module
|
|
103
102
|
function boardOf(view) {
|
|
@@ -53,6 +53,8 @@ export const DESTINATIONS = Object.freeze([
|
|
|
53
53
|
// Not a pm-os-absorb run (run: null): docs-three-way-sync added it — is each linked document the same as its copies.
|
|
54
54
|
Object.freeze({ id: 'docs', label: 'Docs', key: 'g d', module: 'dest-docs.js', run: null }),
|
|
55
55
|
// Not a pm-os-absorb run (run: null): people-directory added it — the people the PM works with in a repository.
|
|
56
|
+
// Not a pm-os-absorb run (run: null): meeting-notes Run 12 added it — every meeting note by initiative, and the note.
|
|
57
|
+
Object.freeze({ id: 'notes', label: 'Notes', key: 'g n', module: 'dest-notes.js', run: null }),
|
|
56
58
|
Object.freeze({ id: 'people', label: 'People', key: 'g p', module: 'dest-people.js', run: null }),
|
|
57
59
|
Object.freeze({ id: 'settings', label: 'Settings', key: null, module: 'dest-settings.js', run: 3 }),
|
|
58
60
|
]);
|
|
@@ -21,13 +21,16 @@
|
|
|
21
21
|
import { el, setChildren } from './dom.js';
|
|
22
22
|
import { button, needsDot, needsCount } from './primitives.js';
|
|
23
23
|
import { COPY } from './frame-copy.js';
|
|
24
|
-
import
|
|
24
|
+
import * as dock from './note-dock.js';
|
|
25
|
+
import { iconButton } from './primitives.js';
|
|
26
|
+
import { el as h } from './dom.js';
|
|
25
27
|
|
|
26
28
|
/** The destinations the bar names, in order. Settings sits at the end of the bar, after Sync. Docs joined the bar at the
|
|
27
29
|
* PM's word (2026-10-06, docs-three-way-sync: "I thought Docs is one of the main menu"). People joined after Docs when the
|
|
28
30
|
* PM approved the People mockup and said build it (2026-10-06, people-directory). My work joined beside Agenda when the PM
|
|
29
|
-
* approved the mockup that puts it there and said build it (2026-10-07, my-work)
|
|
30
|
-
|
|
31
|
+
* approved the mockup that puts it there and said build it (2026-10-07, my-work). Notes joined between Docs and People with the
|
|
32
|
+
* Record control before Settings, as the "Notes: next features" mockup the PM approved (2026-10-07, meeting-notes Run 12). */
|
|
33
|
+
export const BAR_DESTS = Object.freeze(['today', 'mywork', 'initiatives', 'board', 'reports', 'docs', 'notes', 'people']);
|
|
31
34
|
|
|
32
35
|
/** Is this sync reading "not in sync"? Behind, ahead, records not yet recorded, or `remote_stale`: no fetch in the last
|
|
33
36
|
* few minutes, so another machine's push would be invisible here and "in sync" cannot be claimed. An unreadable status
|
|
@@ -38,7 +41,10 @@ export function unsyncedOf(st) {
|
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
/** The lucide symbol each place wears in the bar (UI comfort, 2026-10-07: a word alone is slow to find; the phone's tab bar already wears these). */
|
|
41
|
-
const NAV_ICON = Object.freeze({ today: 'house', mywork: 'list-checks', initiatives: 'target', board: 'square-kanban', reports: 'chart-column', docs: 'file-text', people: 'users', settings: 'settings' });
|
|
44
|
+
const NAV_ICON = Object.freeze({ today: 'house', mywork: 'list-checks', initiatives: 'target', board: 'square-kanban', reports: 'chart-column', docs: 'file-text', notes: 'notebook-pen', people: 'users', settings: 'settings' });
|
|
45
|
+
|
|
46
|
+
const fmtClock = (ms) => { const t = Math.max(0, Math.floor(ms / 1000)); const m = Math.floor(t / 60); return (m >= 60 ? Math.floor(m / 60) + ':' + String(m % 60).padStart(2, '0') : String(m)) + ':' + String(t % 60).padStart(2, '0'); };
|
|
47
|
+
const dockHighlight = () => (dock.highlightBusy ? dock.highlightBusy() : null);
|
|
42
48
|
|
|
43
49
|
export function createTopbar({ go, openPalette, openView, repoMenu, sync } = {}) {
|
|
44
50
|
const node = el('header', { class: 'fr-bar', 'data-shell': 'top', 'data-fr': 'bar' });
|
|
@@ -47,12 +53,6 @@ export function createTopbar({ go, openPalette, openView, repoMenu, sync } = {})
|
|
|
47
53
|
let repoLabel = null;
|
|
48
54
|
const repoSlot = el('div', { class: 'fr-bar__repo' });
|
|
49
55
|
|
|
50
|
-
const recordingIndicator = el('span', {
|
|
51
|
-
class: 'fr-recording-dot', 'data-fr-recording': '',
|
|
52
|
-
title: 'Notes are being recorded',
|
|
53
|
-
'aria-hidden': 'true',
|
|
54
|
-
});
|
|
55
|
-
|
|
56
56
|
const count = needsCount('0', { 'data-today-badge': '' });
|
|
57
57
|
const dot = needsDot();
|
|
58
58
|
const links = {};
|
|
@@ -87,11 +87,36 @@ export function createTopbar({ go, openPalette, openView, repoMenu, sync } = {})
|
|
|
87
87
|
});
|
|
88
88
|
search.appendChild(el('kbd', { class: 'fr-kbd' }, COPY.searchKey));
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
/* The Record control (meeting-notes Run 12, mockup board 2), right of the bar before Settings. Idle: Record. Recording: the
|
|
91
|
+
* red dot and the clock (a press opens the note), the highlight and Pause. Paused: the clock and Resume. Stop is in the note. */
|
|
92
|
+
const recSlot = el('div', { class: 'fr-rec', 'data-fr': 'record' });
|
|
93
|
+
let recKey = '';
|
|
94
|
+
let recTick = null;
|
|
95
|
+
const recClock = () => { const st = dock.recordState(); return st.started ? fmtClock(Date.now() - st.started) : ''; };
|
|
96
|
+
function drawRecord() {
|
|
97
|
+
const st = dock.recordState();
|
|
98
|
+
const busy = st.mode === 'recording' && dockHighlight();
|
|
99
|
+
const key = st.mode + ':' + st.id + ':' + (busy ? busy.state + busy.open : '');
|
|
100
|
+
if (key === recKey) return;
|
|
101
|
+
recKey = key;
|
|
102
|
+
clearInterval(recTick); recTick = null;
|
|
103
|
+
if (st.mode === 'idle') {
|
|
104
|
+
setChildren(recSlot, [button({ label: 'Record', variant: 'outline', icon: 'mic', attrs: { class: 'fr-rec__start', 'data-fr-rec': 'idle', title: 'Start a note and record the meeting' }, onClick: (e) => dock.recordNew(e && e.currentTarget) })]);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (st.mode === 'paused') {
|
|
108
|
+
setChildren(recSlot, [button({ label: 'Resume', variant: 'outline', icon: 'mic', attrs: { class: 'fr-rec__start', 'data-fr-rec': 'paused', title: 'Resume recording ' + (st.title || 'the note') }, onClick: () => dock.resumeNow() })]);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const clock = h('span', { class: 'fr-rec__clock', 'data-fr-rec-clock': '' }, recClock());
|
|
112
|
+
recTick = setInterval(() => { clock.textContent = recClock(); }, 1000);
|
|
113
|
+
const open = button({ label: h('span', { class: 'fr-rec__txt' }, h('span', { class: 'fr-rec-dot', 'aria-hidden': 'true' }), clock), variant: 'ghost',
|
|
114
|
+
attrs: { class: 'fr-rec__seg fr-rec__open', 'data-fr-rec': 'recording', 'aria-label': 'Recording ' + (st.title || 'a note') + '. Open the note.' }, onClick: () => dock.openDock(st.id) });
|
|
115
|
+
const hl = iconButton({ icon: 'highlighter', ariaLabel: busy ? 'Highlight in progress: show it' : 'Highlight the last 30 seconds and what is said next',
|
|
116
|
+
attrs: { class: 'fr-rec__seg' + (busy ? ' fr-rec__seg--busy' : ''), 'data-fr-rec-highlight': busy ? busy.state : '' }, onClick: () => dock.highlightNow() });
|
|
117
|
+
const pause = iconButton({ icon: 'pause', ariaLabel: 'Pause recording', attrs: { class: 'fr-rec__seg', 'data-fr-rec-pause': '' }, onClick: () => dock.pauseNow() });
|
|
118
|
+
setChildren(recSlot, [h('div', { class: 'fr-rec__group', role: 'group', 'aria-label': 'Recording' }, open, hl, pause)]);
|
|
119
|
+
}
|
|
95
120
|
|
|
96
121
|
let syncBtn = null;
|
|
97
122
|
let syncLabel = null;
|
|
@@ -129,8 +154,8 @@ export function createTopbar({ go, openPalette, openView, repoMenu, sync } = {})
|
|
|
129
154
|
syncLabel = want;
|
|
130
155
|
syncBtn = want ? syncButton(want, !!model.syncing) : null;
|
|
131
156
|
}
|
|
132
|
-
|
|
133
|
-
setChildren(node, [repoSlot,
|
|
157
|
+
drawRecord();
|
|
158
|
+
setChildren(node, [repoSlot, nav, search, syncBtn, recSlot, links.settings]);
|
|
134
159
|
}
|
|
135
160
|
|
|
136
161
|
update({});
|
|
@@ -39,14 +39,12 @@
|
|
|
39
39
|
* POST /api/notes/new · save · dictate · record · transcript · audio · label · speaker · switch · whisper-setup · private
|
|
40
40
|
* · summarize · summarize-agent · delete · line · recording-delete · link
|
|
41
41
|
*/
|
|
42
|
-
import { el } from './dom.js';
|
|
42
|
+
import { el, kept } from './dom.js';
|
|
43
43
|
import { askConfirm, askText } from './dialogs.js';
|
|
44
44
|
import { pmGet, pmPost } from './http.js';
|
|
45
45
|
import { mdRender } from './markdown.js';
|
|
46
46
|
import { icon } from './icons.js';
|
|
47
47
|
import { button, iconButton, field, row, sheet, empty, menu, toast, ensureComponentsCss } from './primitives.js';
|
|
48
|
-
import { startHighlight, updateListenProgress, formatListenRemaining, addHighlightToNote } from '/lib/notes-highlight.js';
|
|
49
|
-
import { checkConflict, collectRepoPassages, formatConflict } from '/lib/notes-conflicts.js';
|
|
50
48
|
|
|
51
49
|
const SAVE_MS = 700; // a pause this long in typing saves the note
|
|
52
50
|
const POLL_MS = 1500; // while listening, and until the last line is labelled, the note is read this often
|
|
@@ -66,6 +64,10 @@ let speech = null; // the browser recogniser: { mode: 'dictate'|'reco
|
|
|
66
64
|
let audio = null; // local Whisper capture: { id, state, sources, ctx, streams, started, clock, msg }
|
|
67
65
|
let engine = null; // { ws, whisper, jev, at }
|
|
68
66
|
let showList = false;
|
|
67
|
+
let inlineDraw = false; // the pop-up is drawing: the fields and the player it shows are left in place (pm/note-dock.js)
|
|
68
|
+
/** A node this view keeps across redraws (an editor field, the player). The pop-up patches itself (dom.js patchChildren), so
|
|
69
|
+
* there the node is a placeholder and stays where it is: a field that is typed in is never moved, and keeps its focus. */
|
|
70
|
+
const own = (node) => (inlineDraw ? kept(node) : node);
|
|
69
71
|
let pollTimer = null; let agentTimer = null;
|
|
70
72
|
let busy = '';
|
|
71
73
|
let player = null; // the replay: { id, bytes, el, playing }, kept across redraws so playback goes on
|
|
@@ -735,161 +737,119 @@ async function startSpeech(ctx, n, mode, install = false) {
|
|
|
735
737
|
|
|
736
738
|
/* ── highlighting during recording (Run 11) ─────────────────────────────────────── */
|
|
737
739
|
/** Start a highlight: capture last 30s, listen forward for the chosen duration. */
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
if (highlight) {
|
|
759
|
-
highlight.state = 'transcribing';
|
|
760
|
-
ctx.setState({});
|
|
761
|
-
// Simulate transcription
|
|
762
|
-
setTimeout(() => {
|
|
763
|
-
if (highlight) {
|
|
764
|
-
highlight.text = 'Transcribed highlight text here.';
|
|
765
|
-
highlight.state = 'labeling';
|
|
766
|
-
ctx.setState({});
|
|
767
|
-
// Simulate labeling
|
|
768
|
-
setTimeout(() => {
|
|
769
|
-
if (highlight) {
|
|
770
|
-
highlight.label = 'Statement';
|
|
771
|
-
highlight.state = 'checking';
|
|
772
|
-
ctx.setState({});
|
|
773
|
-
// Simulate checking
|
|
774
|
-
setTimeout(() => {
|
|
775
|
-
if (highlight) {
|
|
776
|
-
highlight.state = 'done';
|
|
777
|
-
ctx.setState({});
|
|
778
|
-
}
|
|
779
|
-
}, 500);
|
|
780
|
-
}
|
|
781
|
-
}, 300);
|
|
782
|
-
}
|
|
783
|
-
}, 500);
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
}, 100);
|
|
787
|
-
}
|
|
788
|
-
}, 100);
|
|
789
|
-
} catch (e) {
|
|
790
|
-
banner = { tone: 'bad', text: 'Highlight failed: ' + e.message };
|
|
791
|
-
ctx.setState({});
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
/** Close the highlight pop-up (work continues in background). */
|
|
796
|
-
function closeHighlight(ctx) {
|
|
797
|
-
highlight = null;
|
|
798
|
-
if (highlightTimer) clearInterval(highlightTimer);
|
|
799
|
-
highlightTimer = null;
|
|
740
|
+
/* ── highlights (meeting-notes Run 12, mockup board 0) ──────────────────────────────────────────────────────────────
|
|
741
|
+
* A press keeps the last 30 seconds and listens for 30 s, 1 min or 2 min more (the PM can change it while it listens).
|
|
742
|
+
* When the window closes and its audio is transcribed, POST /api/notes/highlight makes the highlight from the transcript
|
|
743
|
+
* lines said in the window: their words, the strongest label among them (Jev's when it is on), and the closest file in
|
|
744
|
+
* the repository with Jev's comparison when the check is on. The pop-up shows each step as it happens; closing it does not
|
|
745
|
+
* stop the work, and the highlight is kept in the note. Nothing here is made up: a window with no transcribed line says so. */
|
|
746
|
+
const HL_BEFORE_MS = 30 * 1000;
|
|
747
|
+
const HL_AFTER = [['30s', 30 * 1000, '30 s'], ['1min', 60 * 1000, '1 min'], ['2min', 120 * 1000, '2 min']];
|
|
748
|
+
const hlAfterMs = () => (HL_AFTER.find((x) => x[0] === highlightListenDuration) || HL_AFTER[0])[1];
|
|
749
|
+
let hlOpen = false;
|
|
750
|
+
/** Highlight on the note that is listening: from the Record control, the note's own button, or H. */
|
|
751
|
+
export function highlightNow(ctx, id) {
|
|
752
|
+
const n = cache.get(id);
|
|
753
|
+
const at = liveMs(n);
|
|
754
|
+
if (at === null) return false;
|
|
755
|
+
if (highlight && highlight.state === 'listening') { hlOpen = true; ctx.setState({}); return true; }
|
|
756
|
+
highlight = { id: n.id, title: n.title || '', at, from: Math.max(0, at - HL_BEFORE_MS), state: 'listening', pressed: Date.now(), result: null, error: null };
|
|
757
|
+
hlOpen = true;
|
|
758
|
+
clearInterval(highlightTimer);
|
|
759
|
+
highlightTimer = setInterval(() => hlTick(ctx), 500);
|
|
800
760
|
ctx.setState({});
|
|
761
|
+
return true;
|
|
801
762
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
763
|
+
function hlTick(ctx) {
|
|
764
|
+
const h = highlight;
|
|
765
|
+
if (!h) { clearInterval(highlightTimer); highlightTimer = null; return; }
|
|
766
|
+
const live = cache.get(h.id) && listening(cache.get(h.id));
|
|
767
|
+
// the window closes when its time is up, or when listening stops first
|
|
768
|
+
if (h.state === 'listening' && (Date.now() - h.pressed >= hlAfterMs() || !live)) {
|
|
769
|
+
clearInterval(highlightTimer); highlightTimer = null;
|
|
770
|
+
h.to = h.at + Math.min(hlAfterMs(), Date.now() - h.pressed);
|
|
771
|
+
finishHighlight(ctx, h);
|
|
810
772
|
}
|
|
773
|
+
const t = typeof document !== 'undefined' && document.querySelector('[data-nt-hl-left]');
|
|
774
|
+
if (t) t.textContent = hlLeft(h); else ctx.setState({});
|
|
811
775
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
});
|
|
823
|
-
banner = { tone: 'good', text: 'Task created: ' + title };
|
|
824
|
-
ctx.setState({});
|
|
825
|
-
setTimeout(() => { banner = null; ctx.setState({}); }, 2000);
|
|
826
|
-
} catch (e) {
|
|
827
|
-
banner = { tone: 'bad', text: 'Failed to create task: ' + e.message };
|
|
828
|
-
ctx.setState({});
|
|
776
|
+
const hlLeft = (h) => fmtTime(Math.min(Date.now() - h.pressed, hlAfterMs())) + ' of ' + (HL_AFTER.find((x) => x[0] === highlightListenDuration) || HL_AFTER[0])[2];
|
|
777
|
+
async function finishHighlight(ctx, h) {
|
|
778
|
+
h.state = 'transcribing'; ctx.setState({});
|
|
779
|
+
// the window's last audio is still being transcribed: wait for it (at most 30 s)
|
|
780
|
+
const t0 = Date.now();
|
|
781
|
+
for (;;) {
|
|
782
|
+
const r = await loadNote(ctx, h.id);
|
|
783
|
+
const done = r && r.ok && !(r.audio && r.audio.pending) && (r.note.transcript || []).every((x) => !(typeof x.t === 'number' && x.t <= h.to && x.open));
|
|
784
|
+
if (done || Date.now() - t0 > 30000 || highlight !== h) break;
|
|
785
|
+
await new Promise((res) => setTimeout(res, 1000));
|
|
829
786
|
}
|
|
787
|
+
if (highlight !== h) return;
|
|
788
|
+
h.state = 'checking'; ctx.setState({});
|
|
789
|
+
const r = await pmPost('/api/notes/highlight', { id: h.id, from: h.from, to: h.to });
|
|
790
|
+
if (highlight !== h) return;
|
|
791
|
+
if (r && r.ok) { h.result = r.highlight; h.checked = r.checked; h.jev = r.jev; h.state = 'done'; await loadNote(ctx, h.id); }
|
|
792
|
+
else { h.state = 'failed'; h.error = (r && r.error) || 'the highlight was not made'; }
|
|
793
|
+
hlOpen = true;
|
|
794
|
+
ctx.setState({});
|
|
830
795
|
}
|
|
796
|
+
function closeHighlight(ctx) {
|
|
797
|
+
// closing while it still works only hides the pop-up; a finished one is let go
|
|
798
|
+
if (highlight && (highlight.state === 'done' || highlight.state === 'failed')) highlight = null;
|
|
799
|
+
hlOpen = false;
|
|
800
|
+
ctx.setState({});
|
|
801
|
+
}
|
|
802
|
+
async function removeHighlight(ctx, h) {
|
|
803
|
+
if (h.result) await pmPost('/api/notes/highlight-remove', { id: h.id, hid: h.result.id });
|
|
804
|
+
highlight = null; hlOpen = false;
|
|
805
|
+
await loadNote(ctx, h.id);
|
|
806
|
+
ctx.setState({});
|
|
807
|
+
}
|
|
808
|
+
/** Is a highlight being made, and is its result waiting to be seen? (the Record control's dot) */
|
|
809
|
+
export const highlightBusy = () => (highlight ? { state: highlight.state, open: hlOpen } : null);
|
|
810
|
+
export function openHighlight(ctx) { if (highlight) { hlOpen = true; ctx.setState({}); } }
|
|
831
811
|
|
|
832
|
-
/**
|
|
833
|
-
function
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
{
|
|
812
|
+
/** The highlight's pop-up (pm/note-dock.js draws it on every place), or null when there is none or it is closed. */
|
|
813
|
+
export function highlightPanel(ctx) {
|
|
814
|
+
const h = highlight;
|
|
815
|
+
if (!h || !hlOpen) return null;
|
|
816
|
+
const order = ['listening', 'transcribing', 'checking', 'done'];
|
|
817
|
+
const at = order.indexOf(h.state);
|
|
818
|
+
const step = (k, label, extra) => {
|
|
819
|
+
const i = order.indexOf(k);
|
|
820
|
+
const done = h.state === 'done' || (at > i && h.state !== 'failed');
|
|
821
|
+
const now = h.state === k;
|
|
822
|
+
return el('li', { class: 'nt-hl-step' + (done ? ' nt-hl-step--done' : now ? ' nt-hl-step--now' : ''), 'data-nt-hl-step': k },
|
|
823
|
+
el('span', { class: 'nt-hl-step__mk' }, icon(done ? 'check' : 'circle-dot')), el('span', null, label), extra || null);
|
|
824
|
+
};
|
|
825
|
+
const durs = el('div', { class: 'nt-hl-durs', role: 'group', 'aria-label': 'Listen after' }, HL_AFTER.map(([k, , label]) => button({ label, variant: highlightListenDuration === k ? 'secondary' : 'ghost', size: 'sm',
|
|
826
|
+
onClick: () => { highlightListenDuration = k; ctx.setState({}); }, attrs: { 'data-nt-duration': k, 'aria-pressed': highlightListenDuration === k ? 'true' : 'false' } })));
|
|
827
|
+
const body = [
|
|
828
|
+
el('div', { class: 'nt-hl-head' }, el('h3', { class: 'nt-hl-title' }, 'Highlight'), el('span', { class: 'nt-hl-at' }, fmtTime(h.from) + (h.to ? '–' + fmtTime(h.to) : ' →')),
|
|
829
|
+
h.result ? el('span', { class: 'nt-hl-tag' }, h.result.label) : null),
|
|
830
|
+
el('ol', { class: 'nt-hl-steps' },
|
|
831
|
+
el('li', { class: 'nt-hl-step nt-hl-step--done' }, el('span', { class: 'nt-hl-step__mk' }, icon('check')), el('span', null, 'Kept the last 30 seconds')),
|
|
832
|
+
step('listening', 'Listening', h.state === 'listening' ? el('span', { class: 'nt-hl-left', 'data-nt-hl-left': '' }, hlLeft(h)) : null),
|
|
833
|
+
step('transcribing', 'Transcribe the window'),
|
|
834
|
+
step('checking', 'Label it and check the repository'),
|
|
835
|
+
),
|
|
836
|
+
h.state === 'listening' ? durs : null,
|
|
843
837
|
];
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
});
|
|
859
|
-
|
|
860
|
-
const durationOptions = el('div', { class: 'nt-highlight__durations' });
|
|
861
|
-
['30s', '1min', '2min'].forEach((dur) => {
|
|
862
|
-
const opt = button({
|
|
863
|
-
label: dur,
|
|
864
|
-
variant: highlightListenDuration === dur ? 'primary' : 'secondary',
|
|
865
|
-
onClick: () => {
|
|
866
|
-
highlightListenDuration = dur;
|
|
867
|
-
ctx.setState({});
|
|
868
|
-
},
|
|
869
|
-
attrs: { class: 'nt-highlight__duration-btn', 'data-nt-duration': dur }
|
|
870
|
-
});
|
|
871
|
-
durationOptions.appendChild(opt);
|
|
872
|
-
});
|
|
873
|
-
|
|
874
|
-
const actions = el('div', { class: 'nt-highlight__actions' });
|
|
875
|
-
actions.appendChild(button({
|
|
876
|
-
label: highlight.state === 'done' ? 'Accept' : 'Cancel',
|
|
877
|
-
variant: 'primary',
|
|
878
|
-
onClick: () => {
|
|
879
|
-
if (highlight.state === 'done') acceptHighlight(ctx, n);
|
|
880
|
-
else closeHighlight(ctx);
|
|
881
|
-
},
|
|
882
|
-
attrs: { 'data-nt-highlight-accept': '' }
|
|
883
|
-
}));
|
|
884
|
-
|
|
885
|
-
const popup = el('div', { class: 'nt-highlight__popup', 'data-nt-part': 'highlight-popup' },
|
|
886
|
-
el('div', { class: 'nt-highlight__header' }, el('h3', {}, 'Recording Highlight')),
|
|
887
|
-
progress,
|
|
888
|
-
highlight.state === 'listening' ? durationOptions : null,
|
|
889
|
-
actions
|
|
890
|
-
);
|
|
891
|
-
|
|
892
|
-
return popup;
|
|
838
|
+
if (h.state === 'done' && h.result) {
|
|
839
|
+
const c = h.result.check;
|
|
840
|
+
body.push(el('p', { class: 'nt-hl-text', 'data-nt-hl-text': '' }, h.result.text));
|
|
841
|
+
body.push(el('p', { class: 'nt-hint', 'data-nt-hl-check': c ? c.verdict : 'none' }, c && c.doc
|
|
842
|
+
? (c.verdict === 'differs' ? 'Differs from ' : c.verdict === 'agrees' ? 'Agrees with ' : 'Closest file: ') + c.doc + (c.line ? ':' + c.line : '') + (c.by === 'jev' ? '' : ' (found on this machine; turn on the Jev check in Notes settings to compare)')
|
|
843
|
+
: h.checked ? 'No file in the repository talks about this.' : 'Not checked: the note is not linked to a repository.'));
|
|
844
|
+
}
|
|
845
|
+
if (h.state === 'failed') body.push(el('p', { class: 'nt-hint nt-hint--bad', 'data-nt-hl-failed': '' }, 'No highlight: ' + h.error + '.'));
|
|
846
|
+
const acts = [];
|
|
847
|
+
if (h.state === 'listening') acts.push(button({ label: 'Stop listening now', variant: 'outline', size: 'sm', onClick: () => { h.pressed = Date.now() - hlAfterMs(); hlTick(ctx); }, attrs: { 'data-nt-hl-stop': '' } }));
|
|
848
|
+
if (h.state === 'done') acts.push(button({ label: 'Done', variant: 'primary', size: 'sm', onClick: () => closeHighlight(ctx), attrs: { 'data-nt-hl-done': '' } }),
|
|
849
|
+
button({ label: 'Remove', variant: 'ghost', size: 'sm', onClick: () => removeHighlight(ctx, h), attrs: { 'data-nt-hl-remove': '' } }));
|
|
850
|
+
else acts.push(button({ label: 'Close', variant: 'ghost', size: 'sm', onClick: () => closeHighlight(ctx), attrs: { 'data-nt-hl-close': '' } }));
|
|
851
|
+
body.push(el('div', { class: 'nt-hl-acts' }, acts));
|
|
852
|
+
return el('section', { class: 'nt-hl', role: 'dialog', 'aria-label': 'Highlight', 'data-nt-part': 'highlight-popup', 'data-nt-hl': h.state }, body);
|
|
893
853
|
}
|
|
894
854
|
|
|
895
855
|
async function stopRecording(ctx) {
|
|
@@ -905,6 +865,17 @@ async function stopRecording(ctx) {
|
|
|
905
865
|
ctx.setState({});
|
|
906
866
|
}
|
|
907
867
|
|
|
868
|
+
/** Stop in the note: listening stops and the note leaves the Record control at the top right (pm/note-dock.js). */
|
|
869
|
+
async function stopAndFinish(ctx) {
|
|
870
|
+
const d = await import('./note-dock.js');
|
|
871
|
+
if (d.recordState().mode !== 'idle') await d.finish(); else await stopRecording(ctx);
|
|
872
|
+
}
|
|
873
|
+
/** Resume in the note: the Record control holds it again and it listens. */
|
|
874
|
+
async function resumeOn(n) {
|
|
875
|
+
const d = await import('./note-dock.js');
|
|
876
|
+
await d.recordNote(n.id, n.title);
|
|
877
|
+
}
|
|
878
|
+
|
|
908
879
|
/* ── the two sheets: what to listen to, and settings ─────────────────────────────────────────── */
|
|
909
880
|
async function chooseSources(ctx, n) {
|
|
910
881
|
const eng = engine && engine.ws === wsOf(ctx) && engine.whisper ? engine : await loadEngine(ctx);
|
|
@@ -1193,22 +1164,22 @@ function header(ctx, n, ed, opts = {}) {
|
|
|
1193
1164
|
el('span', { 'data-nt-sources': '' }, ' · ' + (live.state === 'starting' || live.state === 'checking' ? 'starting' : ['mic', 'meeting'].filter((s) => live.sources.includes(s)).join(' + '))),
|
|
1194
1165
|
el('span', { class: 'nt-sr' }, ' Notes are being taken.'));
|
|
1195
1166
|
// Highlight button (Run 11)
|
|
1196
|
-
acts.push(iconButton({ icon: '
|
|
1197
|
-
acts.push(button({ label: 'Stop', variant: 'outline', size: 'sm', onClick: () =>
|
|
1167
|
+
acts.push(iconButton({ icon: 'highlighter', ariaLabel: 'Highlight the last 30 seconds and what is said next', onClick: () => highlightNow(ctx, n.id), attrs: { 'data-nt-highlight': '' } }));
|
|
1168
|
+
acts.push(button({ label: 'Stop', variant: 'outline', size: 'sm', onClick: () => stopAndFinish(ctx), attrs: { 'data-nt-stop': '' } }));
|
|
1198
1169
|
} else {
|
|
1199
1170
|
const len = (n.transcript || []).length ? fmtTime(Math.max(...(n.transcript || []).map((x) => x.t || 0))) : '';
|
|
1200
1171
|
status = el('span', { class: 'nt-live' }, el('b', null, 'Meeting note'), len ? el('span', null, len) : null);
|
|
1201
1172
|
if (!n.private) acts.push((n.transcript || []).length
|
|
1202
|
-
? button({ label: '
|
|
1173
|
+
? button({ label: 'Resume', variant: 'primary', size: 'sm', onClick: () => resumeOn(n), attrs: { 'data-nt-listen': 'again' } })
|
|
1203
1174
|
: button({ label: 'Listen', variant: 'primary', size: 'sm', onClick: () => chooseSources(ctx, n), attrs: { 'data-nt-listen': '' } }));
|
|
1204
1175
|
}
|
|
1205
1176
|
if (opts.onMinimize) acts.push(button({ label: 'Minimize', variant: 'ghost', size: 'sm', onClick: opts.onMinimize, attrs: { 'data-nt-minimize': '' } }));
|
|
1206
1177
|
if (!opts.inline) acts.push(button({ label: 'All notes', variant: 'ghost', size: 'sm', onClick: () => { showList = !showList; ctx.setState({}); if (showList) loadList(ctx); }, attrs: { 'data-nt-all': '', 'aria-expanded': showList ? 'true' : 'false' } }));
|
|
1207
|
-
const more = iconButton({ icon: 'ellipsis', ariaLabel: 'More', onClick: () => moreMenu(ctx, n, opts, more), attrs: { 'data-nt-more': '' } });
|
|
1178
|
+
const more = iconButton({ icon: 'ellipsis', ariaLabel: 'More', onClick: (e) => moreMenu(ctx, n, opts, (e && e.currentTarget) || more), attrs: { 'data-nt-more': '' } });
|
|
1208
1179
|
acts.push(more);
|
|
1209
1180
|
return el('header', { class: 'nt-header', 'data-nt-part': 'header' },
|
|
1210
1181
|
el('div', { class: 'nt-bar' }, status, el('div', { class: 'nt-acts' }, acts)),
|
|
1211
|
-
el('div', { class: 'nt-headl' }, ed.titleWrap, whenLine(ctx, n), peopleRow(n)));
|
|
1182
|
+
el('div', { class: 'nt-headl' }, own(ed.titleWrap), whenLine(ctx, n), peopleRow(n)));
|
|
1212
1183
|
}
|
|
1213
1184
|
function listPane(ctx) {
|
|
1214
1185
|
const rows = (list && list.notes) || [];
|
|
@@ -1240,7 +1211,7 @@ function itemRow(ctx, n, key, title, x) {
|
|
|
1240
1211
|
const actions = [];
|
|
1241
1212
|
// Add "Make task" button for action items (Run 11)
|
|
1242
1213
|
if (key === 'action') {
|
|
1243
|
-
actions.push(iconButton({ icon: 'plus', ariaLabel: 'Make a task from this', onClick: () => makeTask(
|
|
1214
|
+
actions.push(iconButton({ icon: 'plus', ariaLabel: 'Make a task from this', onClick: (e) => import('./notes-task.js').then((m) => m.makeTask(n, x, { repo: ctx.state && ctx.state.product, btn: e && e.currentTarget })), size: 'sm', attrs: { 'data-nt-make-task': '' } }));
|
|
1244
1215
|
}
|
|
1245
1216
|
// Add remove buttons for unpinned items
|
|
1246
1217
|
if (bullet) {
|
|
@@ -1421,7 +1392,7 @@ function myNotes(ctx, n, ed) {
|
|
|
1421
1392
|
const t = liveMs(n);
|
|
1422
1393
|
const parts = [el('h3', { class: 'nt-sec__h' }, 'Your notes')];
|
|
1423
1394
|
if (items.length) parts.push(el('ul', { class: 'nt-items', 'data-nt-notelines': '' }, items));
|
|
1424
|
-
if (!replaying) parts.push(el('div', { class: 'nt-addrow' }, el('span', { class: 'nt-at', 'data-nt-clock-now': '' }, t !== null ? fmtTime(t) : ''), ed.addWrap));
|
|
1395
|
+
if (!replaying) parts.push(el('div', { class: 'nt-addrow' }, el('span', { class: 'nt-at', 'data-nt-clock-now': '' }, t !== null ? fmtTime(t) : ''), own(ed.addWrap)));
|
|
1425
1396
|
else parts.push(el('p', { class: 'nt-hint' }, 'Your notes, as the meeting plays. Pause to add more.'));
|
|
1426
1397
|
const sp = speech && speech.id === n.id ? speech : null;
|
|
1427
1398
|
const bar = [];
|
|
@@ -1553,11 +1524,6 @@ export function renderBoard(inner, ctx) {
|
|
|
1553
1524
|
wrap.appendChild(el('div', { class: 'nt-body2' }, noteColumn(ctx, n, ed), transcriptColumn(ctx, n)));
|
|
1554
1525
|
if (pl) queueMicrotask(paintNow);
|
|
1555
1526
|
if (((n.agent && n.agent.started && !n.agent.markdown) || finalPending(n)) && !agentTimer) pollAgent(ctx, n.id);
|
|
1556
|
-
// Add highlight pop-up if active
|
|
1557
|
-
if (highlight && n.id === currentId) {
|
|
1558
|
-
const hlPopup = highlightPopup(ctx, n);
|
|
1559
|
-
if (hlPopup) wrap.appendChild(hlPopup);
|
|
1560
|
-
}
|
|
1561
1527
|
}
|
|
1562
1528
|
inner.appendChild(wrap);
|
|
1563
1529
|
keep();
|
|
@@ -1618,16 +1584,20 @@ export function renderInline(host, ctx, id, opts = {}) {
|
|
|
1618
1584
|
if (!n) { loadNote(ctx, id); host.appendChild(el('p', { class: 'nt-hint' }, 'reading the note …')); return true; }
|
|
1619
1585
|
const keep = keepFocus();
|
|
1620
1586
|
const ed = editorFor(ctx, n);
|
|
1587
|
+
inlineDraw = true;
|
|
1588
|
+
try { drawInline(ctx, n, ed, host, opts); } finally { inlineDraw = false; }
|
|
1589
|
+
keep();
|
|
1590
|
+
return true;
|
|
1591
|
+
}
|
|
1592
|
+
function drawInline(ctx, n, ed, host, opts) {
|
|
1621
1593
|
const wrap = el('div', { class: 'nt-wrap nt-wrap--inline', 'data-nt-part': 'inline', 'data-nt-id': n.id });
|
|
1622
1594
|
if (banner) wrap.appendChild(el('div', { class: 'nt-banner' + (banner.tone === 'bad' ? ' nt-banner--bad' : ''), 'data-nt-banner': banner.tone, role: banner.tone === 'bad' ? 'alert' : 'status' }, banner.text));
|
|
1623
1595
|
wrap.appendChild(header(ctx, n, ed, { inline: true, onMinimize: opts.onMinimize }));
|
|
1624
1596
|
if (n.private) wrap.appendChild(el('p', { class: 'nt-hint', 'data-nt-private': '' }, 'This note is private: it is not recorded, and nothing from it goes to Jev or to any agent.'));
|
|
1625
1597
|
const pl = !listening(n) ? playerFor(ctx, n) : null;
|
|
1626
|
-
if (pl) wrap.appendChild(el('div', { class: 'nt-replay', 'data-nt-part': 'replay' }, el('span', { class: 'nt-sec__h' }, 'Replay'), pl.el));
|
|
1598
|
+
if (pl) wrap.appendChild(el('div', { class: 'nt-replay', 'data-nt-part': 'replay' }, el('span', { class: 'nt-sec__h' }, 'Replay'), own(pl.el)));
|
|
1627
1599
|
wrap.appendChild(el('div', { class: 'nt-body2' }, noteColumn(ctx, n, ed), transcriptColumn(ctx, n, { inline: true })));
|
|
1628
1600
|
if (pl) queueMicrotask(paintNow);
|
|
1629
1601
|
if (n.agent && n.agent.started && !n.agent.markdown && !agentTimer) pollAgent(ctx, n.id);
|
|
1630
1602
|
host.appendChild(wrap);
|
|
1631
|
-
keep();
|
|
1632
|
-
return true;
|
|
1633
1603
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* lucide-sprite.js — GENERATED by workspace/third-party/build-lucide-sprite.js from lucide-static 1.52.0.
|
|
2
|
-
* Do not edit by hand. The icons: grip-vertical, plus, check, square, square-check-big, x, undo-2, flag, bot, chevron-right, chevron-down, ellipsis, circle-alert, history, arrow-up, arrow-down, diamond, settings, search, refresh-cw, chevron-left, sun, moon, arrow-left, arrow-right, globe, columns-2, arrow-up-down, arrow-left-right, panel-top, panel-left, maximize-2, minimize-2, git-branch, clock, bold, italic, code, link, type, heading, list, list-checks, text-quote, file-code, git-compare, cloud, share-2, house, target, square-kanban, file-text, square-terminal, calendar, chart-column, users, git-merge, mail, message-square, ticket, ban.
|
|
2
|
+
* Do not edit by hand. The icons: grip-vertical, plus, check, square, square-check-big, x, undo-2, flag, bot, chevron-right, chevron-down, ellipsis, circle-alert, history, arrow-up, arrow-down, diamond, settings, search, refresh-cw, chevron-left, sun, moon, arrow-left, arrow-right, globe, columns-2, arrow-up-down, arrow-left-right, panel-top, panel-left, maximize-2, minimize-2, git-branch, clock, bold, italic, code, link, type, heading, list, list-checks, text-quote, file-code, git-compare, cloud, share-2, house, target, square-kanban, file-text, square-terminal, calendar, chart-column, users, git-merge, mail, message-square, ticket, ban, notebook-pen, mic, pause, play, highlighter, circle-dot.
|
|
3
3
|
*
|
|
4
4
|
* ISC License
|
|
5
5
|
*
|
|
@@ -107,5 +107,11 @@ export const SPRITE = '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true
|
|
|
107
107
|
+ '<symbol id="lucide-message-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z" /></symbol>'
|
|
108
108
|
+ '<symbol id="lucide-ticket" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z" /><path d="M13 5v2" /><path d="M13 17v2" /><path d="M13 11v2" /></symbol>'
|
|
109
109
|
+ '<symbol id="lucide-ban" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M4.929 4.929 19.07 19.071" /></symbol>'
|
|
110
|
+
+ '<symbol id="lucide-notebook-pen" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13.4 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.4" /><path d="M2 6h4" /><path d="M2 10h4" /><path d="M2 14h4" /><path d="M2 18h4" /><path d="M21.378 5.626a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z" /></symbol>'
|
|
111
|
+
+ '<symbol id="lucide-mic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19v3" /><path d="M19 10v2a7 7 0 0 1-14 0v-2" /><rect x="9" y="2" width="6" height="13" rx="3" /></symbol>'
|
|
112
|
+
+ '<symbol id="lucide-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /><rect x="5" y="3" width="5" height="18" rx="1" /></symbol>'
|
|
113
|
+
+ '<symbol id="lucide-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>'
|
|
114
|
+
+ '<symbol id="lucide-highlighter" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 11-6 6v3h9l3-3" /><path d="m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4" /></symbol>'
|
|
115
|
+
+ '<symbol id="lucide-circle-dot" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1" /><circle cx="12" cy="12" r="10" /></symbol>'
|
|
110
116
|
+ '</svg>';
|
|
111
|
-
export const ICONS = ["grip-vertical","plus","check","square","square-check-big","x","undo-2","flag","bot","chevron-right","chevron-down","ellipsis","circle-alert","history","arrow-up","arrow-down","diamond","settings","search","refresh-cw","chevron-left","sun","moon","arrow-left","arrow-right","globe","columns-2","arrow-up-down","arrow-left-right","panel-top","panel-left","maximize-2","minimize-2","git-branch","clock","bold","italic","code","link","type","heading","list","list-checks","text-quote","file-code","git-compare","cloud","share-2","house","target","square-kanban","file-text","square-terminal","calendar","chart-column","users","git-merge","mail","message-square","ticket","ban"];
|
|
117
|
+
export const ICONS = ["grip-vertical","plus","check","square","square-check-big","x","undo-2","flag","bot","chevron-right","chevron-down","ellipsis","circle-alert","history","arrow-up","arrow-down","diamond","settings","search","refresh-cw","chevron-left","sun","moon","arrow-left","arrow-right","globe","columns-2","arrow-up-down","arrow-left-right","panel-top","panel-left","maximize-2","minimize-2","git-branch","clock","bold","italic","code","link","type","heading","list","list-checks","text-quote","file-code","git-compare","cloud","share-2","house","target","square-kanban","file-text","square-terminal","calendar","chart-column","users","git-merge","mail","message-square","ticket","ban","notebook-pen","mic","pause","play","highlighter","circle-dot"];
|