pi-weave 0.2.2 → 0.2.4
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 +12 -5
- package/skills/weave-notepad/SKILL.md +13 -2
- package/src/core/index.ts +2 -0
- package/src/core/links/similar.ts +97 -30
- package/src/core/vault.ts +78 -6
- package/src/pi/tools/noteTool.ts +117 -6
- package/src/web/client/dist/app.js +39 -39
- package/src/web/client/note/Note.tsx +56 -19
- package/src/web/client/note/note.model.ts +101 -24
- package/src/web/client/shell/theme.ts +2 -5
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ## The editor
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
* mode, so text and mode cannot disagree. No reducer — the 686-line one that
|
|
11
|
+
* The explicit Edit control swaps the body for a `<textarea>`; `draft` is
|
|
12
|
+
* `null` in read mode, so text and mode cannot disagree. No reducer — the 686-line one that
|
|
13
13
|
* stood here resolved conflicts against a revision the server no longer
|
|
14
14
|
* issues. Being component state, the draft is out of the 2 s poll's reach;
|
|
15
15
|
* the shell guards it through the `editor` slot below.
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* ## Wikilink preview (P6.3)
|
|
24
24
|
*
|
|
25
25
|
* Hovering or focusing a wikilink opens a small card for its target. The
|
|
26
|
-
* delegated
|
|
26
|
+
* body's delegated handlers extend to the card for
|
|
27
27
|
* free — `mouseover`, `focusin` and `keydown` are read through the same
|
|
28
28
|
* ancestor walk (`previewAnchorOf`), the state machine is `reducePreview`,
|
|
29
29
|
* and this file's only contribution is forwarding the pointer's viewport
|
|
@@ -46,17 +46,18 @@ import {
|
|
|
46
46
|
artifactKeyOfNode,
|
|
47
47
|
DISCARD_PROMPT,
|
|
48
48
|
DONE_LABEL,
|
|
49
|
+
EDIT_LABEL,
|
|
49
50
|
EDITED_WORD,
|
|
50
51
|
EDITOR_ARIA_LABEL,
|
|
51
52
|
EMPTY_PREVIEW,
|
|
52
53
|
PREVIEW_ID,
|
|
53
54
|
SAVE_LABEL,
|
|
54
55
|
SAVING_LABEL,
|
|
56
|
+
TASK_CHECKBOX_ATTR,
|
|
55
57
|
WIKILINK_ATTR,
|
|
56
58
|
draftDirty,
|
|
57
59
|
draftMoved,
|
|
58
60
|
hasTextSelection,
|
|
59
|
-
noteClickAction,
|
|
60
61
|
saveLanded,
|
|
61
62
|
noteEmptyMessage,
|
|
62
63
|
noteHeader,
|
|
@@ -67,6 +68,9 @@ import {
|
|
|
67
68
|
renderNote,
|
|
68
69
|
selectedArtifactPath,
|
|
69
70
|
tagLabel,
|
|
71
|
+
taskCheckboxIndexOf,
|
|
72
|
+
taskCheckboxLabel,
|
|
73
|
+
toggleTaskCheckbox,
|
|
70
74
|
wikiIndex,
|
|
71
75
|
wikilinkTargetOf,
|
|
72
76
|
} from "./note.model";
|
|
@@ -117,30 +121,28 @@ function Header({
|
|
|
117
121
|
open,
|
|
118
122
|
editing,
|
|
119
123
|
saving,
|
|
120
|
-
|
|
124
|
+
onToggle,
|
|
121
125
|
onSave,
|
|
122
126
|
}: {
|
|
123
127
|
view: NoteHeaderView;
|
|
124
128
|
open: () => void;
|
|
125
129
|
editing: boolean;
|
|
126
130
|
saving: boolean;
|
|
127
|
-
|
|
131
|
+
onToggle: () => void;
|
|
128
132
|
onSave: () => void;
|
|
129
133
|
}) {
|
|
130
134
|
return (
|
|
131
135
|
<header class="weave-note-head">
|
|
132
136
|
<h3 class="weave-note-title">{view.title}</h3>
|
|
133
137
|
<p class="weave-note-meta">
|
|
134
|
-
{/* Nothing in read mode: the prose is the way in. The way out needs a
|
|
135
|
-
control, because "click the text" cannot also mean "stop". */}
|
|
136
138
|
{editing ? (
|
|
137
139
|
<>
|
|
138
140
|
<button type="button" class="weave-note-save" disabled={saving} onClick={onSave}>
|
|
139
141
|
{saving ? SAVING_LABEL : SAVE_LABEL}
|
|
140
142
|
</button>
|
|
141
|
-
<button type="button" class="weave-note-edit" onClick={
|
|
143
|
+
<button type="button" class="weave-note-edit" onClick={onToggle}>{DONE_LABEL}</button>
|
|
142
144
|
</>
|
|
143
|
-
) :
|
|
145
|
+
) : <button type="button" class="weave-note-edit" onClick={onToggle}>{EDIT_LABEL}</button>}
|
|
144
146
|
<button type="button" class="weave-note-open" title="Open in $EDITOR" aria-label="Open in $EDITOR" onClick={open}>
|
|
145
147
|
<span class="weave-note-open-mark" aria-hidden="true">↗</span>
|
|
146
148
|
</button>
|
|
@@ -190,6 +192,12 @@ export function Note(props: NoteProps) {
|
|
|
190
192
|
const dispatch = (event: PreviewEvent): void => void sendPreview((state) => reducePreview(state, event));
|
|
191
193
|
const bodyRef = useRef<HTMLDivElement | null>(null);
|
|
192
194
|
const cardRef = useRef<HTMLDivElement | null>(null);
|
|
195
|
+
const taskSaving = useRef(false);
|
|
196
|
+
const taskSource = useRef<{ slug: string; serverBody: string; body: string } | null>(null);
|
|
197
|
+
if (note === null) taskSource.current = null;
|
|
198
|
+
else if (taskSource.current === null || taskSource.current.slug !== note.slug || taskSource.current.serverBody !== note.body) {
|
|
199
|
+
taskSource.current = { slug: note.slug, serverBody: note.body, body: note.body };
|
|
200
|
+
}
|
|
193
201
|
|
|
194
202
|
const [draft, setDraft] = useState<string | null>(null);
|
|
195
203
|
const [saving, setSaving] = useState(false);
|
|
@@ -234,6 +242,33 @@ export function Note(props: NoteProps) {
|
|
|
234
242
|
else close();
|
|
235
243
|
};
|
|
236
244
|
|
|
245
|
+
const saveTask = async (checkbox: HTMLInputElement, taskIndex: number): Promise<void> => {
|
|
246
|
+
const source = taskSource.current;
|
|
247
|
+
const restore = (): void => {
|
|
248
|
+
checkbox.checked = !checkbox.checked;
|
|
249
|
+
checkbox.setAttribute("aria-label", taskCheckboxLabel(checkbox.checked));
|
|
250
|
+
};
|
|
251
|
+
if (source === null || taskSaving.current) {
|
|
252
|
+
restore();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const body = toggleTaskCheckbox(source.body, taskIndex);
|
|
256
|
+
if (body === null) {
|
|
257
|
+
restore();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const inputs = [...(bodyRef.current?.querySelectorAll<HTMLInputElement>(`input[${TASK_CHECKBOX_ATTR}]`) ?? [])];
|
|
262
|
+
taskSaving.current = true;
|
|
263
|
+
checkbox.setAttribute("aria-label", taskCheckboxLabel(checkbox.checked));
|
|
264
|
+
for (const input of inputs) input.disabled = true;
|
|
265
|
+
const ok = await props.onSave(source.slug, body);
|
|
266
|
+
if (ok) source.body = body;
|
|
267
|
+
else restore();
|
|
268
|
+
for (const input of inputs) input.disabled = false;
|
|
269
|
+
taskSaving.current = false;
|
|
270
|
+
};
|
|
271
|
+
|
|
237
272
|
const toggleEdit = (): void => {
|
|
238
273
|
if (note === null) return;
|
|
239
274
|
if (draft === null) open(note.body);
|
|
@@ -326,7 +361,7 @@ export function Note(props: NoteProps) {
|
|
|
326
361
|
open={() => props.onOpen(note.slug)}
|
|
327
362
|
editing={draft !== null}
|
|
328
363
|
saving={saving}
|
|
329
|
-
|
|
364
|
+
onToggle={toggleEdit}
|
|
330
365
|
onSave={() => void save()}
|
|
331
366
|
/>
|
|
332
367
|
{draft !== null ? (
|
|
@@ -381,16 +416,20 @@ export function Note(props: NoteProps) {
|
|
|
381
416
|
}}
|
|
382
417
|
onBlur={() => dispatch({ type: "hide" })}
|
|
383
418
|
onClick={(event) => {
|
|
384
|
-
|
|
419
|
+
const checkbox = event.target as HTMLInputElement;
|
|
420
|
+
const taskIndex = taskCheckboxIndexOf(checkbox);
|
|
421
|
+
if (taskIndex !== null) {
|
|
422
|
+
void saveTask(checkbox, taskIndex);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
385
425
|
const selection =
|
|
386
426
|
typeof window !== "undefined" && typeof window.getSelection === "function"
|
|
387
427
|
? window.getSelection()
|
|
388
428
|
: null;
|
|
429
|
+
if (hasTextSelection(selection)) return;
|
|
389
430
|
const target = wikilinkTargetOf(event.target as unknown as Parameters<typeof wikilinkTargetOf>[0]);
|
|
390
|
-
const action = noteClickAction(hasTextSelection(selection), target);
|
|
391
431
|
// A wikilink carries no href, so route it onto the context bus.
|
|
392
|
-
if (
|
|
393
|
-
else if (action === "edit") open(note.body);
|
|
432
|
+
if (target !== null) props.onSelect(target);
|
|
394
433
|
}}
|
|
395
434
|
onKeyDown={(event) => {
|
|
396
435
|
// Escape is first so the card closes on the gesture a keyboard
|
|
@@ -405,11 +444,9 @@ export function Note(props: NoteProps) {
|
|
|
405
444
|
}
|
|
406
445
|
if (event.key !== "Enter" && event.key !== " ") return;
|
|
407
446
|
const target = wikilinkTargetOf(event.target as unknown as Parameters<typeof wikilinkTargetOf>[0]);
|
|
447
|
+
if (target === null) return;
|
|
408
448
|
event.preventDefault();
|
|
409
|
-
|
|
410
|
-
// mouse-only now that the Edit button is gone.
|
|
411
|
-
if (target !== null) props.onSelect(target);
|
|
412
|
-
else open(note.body);
|
|
449
|
+
props.onSelect(target);
|
|
413
450
|
}}
|
|
414
451
|
// Sanitised by `renderNote`'s three layers — see note.model.ts.
|
|
415
452
|
dangerouslySetInnerHTML={{ __html: html }}
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
* `tsconfig.json` project compile the tests.
|
|
68
68
|
*/
|
|
69
69
|
|
|
70
|
-
import { Marked } from "marked";
|
|
70
|
+
import { Lexer, Marked } from "marked";
|
|
71
71
|
import type { TokenizerAndRendererExtension, Tokens } from "marked";
|
|
72
72
|
import { relTime } from "../../shared/view";
|
|
73
73
|
import type { GraphPayload, ViewNote, WireGraphNode, WireNoteSource } from "../../shared/wire";
|
|
@@ -197,6 +197,14 @@ export function safeUrl(raw: string): string | null {
|
|
|
197
197
|
*/
|
|
198
198
|
export const WIKILINK_ATTR = "data-weave-target";
|
|
199
199
|
|
|
200
|
+
/** The rendered task checkbox's zero-based source-order index. */
|
|
201
|
+
export const TASK_CHECKBOX_ATTR = "data-weave-task";
|
|
202
|
+
|
|
203
|
+
/** The action a task checkbox offers from its current state. */
|
|
204
|
+
export function taskCheckboxLabel(checked: boolean): string {
|
|
205
|
+
return checked ? "Mark task incomplete" : "Mark task complete";
|
|
206
|
+
}
|
|
207
|
+
|
|
200
208
|
/** Prefix core's graph builder gives note nodes. Mirrors `noteSlug`. */
|
|
201
209
|
const NOTE_PREFIX = "note:";
|
|
202
210
|
|
|
@@ -465,11 +473,16 @@ export function renderWikilink(index: WikiIndex, target: string, alias: string):
|
|
|
465
473
|
* rather than `string | Promise<string>`; nothing here is asynchronous.
|
|
466
474
|
*/
|
|
467
475
|
export function markdownRenderer(index: WikiIndex): Marked {
|
|
476
|
+
let taskIndex = 0;
|
|
468
477
|
return new Marked({
|
|
469
478
|
gfm: true,
|
|
470
479
|
async: false,
|
|
471
480
|
extensions: [wikilinkExtension(index)],
|
|
472
481
|
renderer: {
|
|
482
|
+
checkbox({ checked }) {
|
|
483
|
+
const state = checked ? ' checked=""' : "";
|
|
484
|
+
return `<input type="checkbox"${state} ${TASK_CHECKBOX_ATTR}="${taskIndex++}" aria-label="${taskCheckboxLabel(checked)}"> `;
|
|
485
|
+
},
|
|
473
486
|
/**
|
|
474
487
|
* Layer 1. Raw HTML in a note body is rendered as **text**.
|
|
475
488
|
*
|
|
@@ -551,7 +564,6 @@ export interface SanitizeConfig {
|
|
|
551
564
|
readonly ALLOW_DATA_ATTR: boolean;
|
|
552
565
|
readonly ALLOW_ARIA_ATTR: boolean;
|
|
553
566
|
readonly ALLOW_UNKNOWN_PROTOCOLS: boolean;
|
|
554
|
-
readonly USE_PROFILES: { readonly html: true };
|
|
555
567
|
}
|
|
556
568
|
|
|
557
569
|
/**
|
|
@@ -575,9 +587,9 @@ export interface SanitizeConfig {
|
|
|
575
587
|
* and keeps the element. It also contains no `style`, so a CSS-based overlay
|
|
576
588
|
* attack has no attribute to live in.
|
|
577
589
|
*
|
|
578
|
-
* `ALLOW_DATA_ATTR: false` with
|
|
590
|
+
* `ALLOW_DATA_ATTR: false` with the two renderer-owned data attributes named explicitly is
|
|
579
591
|
* narrower than leaving data attributes on: the only `data-` attribute that
|
|
580
|
-
* may exist in rendered output is
|
|
592
|
+
* may exist in rendered output is one this module writes.
|
|
581
593
|
* `ADD_URI_SAFE_ATTR` then tells DOMPurify that the attribute's value is not a
|
|
582
594
|
* URL and must not be run through `IS_ALLOWED_URI` — without it a slug like
|
|
583
595
|
* `note-2` is fine but the check is being applied to a value that was never a
|
|
@@ -644,15 +656,15 @@ export const SANITIZE_CONFIG: SanitizeConfig = frozen({
|
|
|
644
656
|
"align",
|
|
645
657
|
"type",
|
|
646
658
|
"checked",
|
|
647
|
-
"
|
|
659
|
+
"aria-label",
|
|
648
660
|
"start",
|
|
649
661
|
WIKILINK_ATTR,
|
|
662
|
+
TASK_CHECKBOX_ATTR,
|
|
650
663
|
]),
|
|
651
|
-
ADD_URI_SAFE_ATTR: frozen([WIKILINK_ATTR]),
|
|
664
|
+
ADD_URI_SAFE_ATTR: frozen([WIKILINK_ATTR, TASK_CHECKBOX_ATTR]),
|
|
652
665
|
ALLOW_DATA_ATTR: false,
|
|
653
666
|
ALLOW_ARIA_ATTR: false,
|
|
654
667
|
ALLOW_UNKNOWN_PROTOCOLS: false,
|
|
655
|
-
USE_PROFILES: frozen({ html: true as const }),
|
|
656
668
|
});
|
|
657
669
|
|
|
658
670
|
/**
|
|
@@ -671,12 +683,92 @@ export function renderNote(purify: Purifier, body: string, index: WikiIndex): st
|
|
|
671
683
|
|
|
672
684
|
// --- reading a click back out of the DOM --------------------------------------------------
|
|
673
685
|
|
|
686
|
+
const TASK_LINE_RE = /^((?: {0,3}>[ \t]?)*[ \t]*(?:[*+-]|\d{1,9}[.)])[ \t]+\[)([ xX])(\] +\S)/;
|
|
687
|
+
|
|
688
|
+
/** Source offsets for the task markers marked will render, in render order. */
|
|
689
|
+
function taskMarkerOffsets(body: string): Array<number | null> {
|
|
690
|
+
const candidates: number[] = [];
|
|
691
|
+
const source = body.replace(/\r\n?/g, "\n");
|
|
692
|
+
let sourceOffset = 0;
|
|
693
|
+
|
|
694
|
+
for (const token of Lexer.lex(source, { gfm: true })) {
|
|
695
|
+
const tokenOffset = source.indexOf(token.raw, sourceOffset);
|
|
696
|
+
if (tokenOffset === -1) continue;
|
|
697
|
+
sourceOffset = tokenOffset + token.raw.length;
|
|
698
|
+
if (token.type !== "list" && token.type !== "blockquote") continue;
|
|
699
|
+
|
|
700
|
+
let lineOffset = 0;
|
|
701
|
+
while (lineOffset < token.raw.length) {
|
|
702
|
+
const newline = token.raw.indexOf("\n", lineOffset);
|
|
703
|
+
const lineEnd = newline === -1 ? token.raw.length : newline;
|
|
704
|
+
const line = token.raw.slice(lineOffset, lineEnd).replace(/\r$/, "");
|
|
705
|
+
const task = TASK_LINE_RE.exec(line);
|
|
706
|
+
if (task !== null) candidates.push(tokenOffset + lineOffset + (task[1] as string).length);
|
|
707
|
+
lineOffset = newline === -1 ? token.raw.length : newline + 1;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const walker = new Marked();
|
|
712
|
+
const states = (markdown: string): boolean[] => {
|
|
713
|
+
const checked: boolean[] = [];
|
|
714
|
+
walker.walkTokens(Lexer.lex(markdown, { gfm: true }), (token) => {
|
|
715
|
+
if (token.type === "checkbox") checked.push(token.checked);
|
|
716
|
+
});
|
|
717
|
+
return checked;
|
|
718
|
+
};
|
|
719
|
+
const baseline = states(source);
|
|
720
|
+
const offsets: Array<number | null> = baseline.map(() => null);
|
|
721
|
+
// ponytail: re-lex each candidate so Marked remains the sole judge of
|
|
722
|
+
// Markdown structure; use token source positions if Marked adds them.
|
|
723
|
+
for (const offset of candidates) {
|
|
724
|
+
const toggled = `${source.slice(0, offset)}${source[offset] === " " ? "x" : " "}${source.slice(offset + 1)}`;
|
|
725
|
+
const next = states(toggled);
|
|
726
|
+
if (next.length !== baseline.length) continue;
|
|
727
|
+
let changed = -1;
|
|
728
|
+
for (let index = 0; index < baseline.length; index += 1) {
|
|
729
|
+
if (next[index] === baseline[index]) continue;
|
|
730
|
+
if (changed !== -1) {
|
|
731
|
+
changed = -1;
|
|
732
|
+
break;
|
|
733
|
+
}
|
|
734
|
+
changed = index;
|
|
735
|
+
}
|
|
736
|
+
if (changed !== -1) offsets[changed] = offset;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return offsets.map((offset) => {
|
|
740
|
+
if (offset === null) return null;
|
|
741
|
+
let original = 0;
|
|
742
|
+
let normalized = 0;
|
|
743
|
+
while (normalized < offset) {
|
|
744
|
+
original += body[original] === "\r" && body[original + 1] === "\n" ? 2 : 1;
|
|
745
|
+
normalized += 1;
|
|
746
|
+
}
|
|
747
|
+
return original;
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/** Flip one rendered GFM task marker without changing any other source byte. */
|
|
752
|
+
export function toggleTaskCheckbox(body: string, index: number): string | null {
|
|
753
|
+
if (!Number.isSafeInteger(index) || index < 0) return null;
|
|
754
|
+
const offset = taskMarkerOffsets(body)[index];
|
|
755
|
+
if (offset === undefined || offset === null) return null;
|
|
756
|
+
return `${body.slice(0, offset)}${body[offset] === " " ? "x" : " "}${body.slice(offset + 1)}`;
|
|
757
|
+
}
|
|
758
|
+
|
|
674
759
|
/** The slice of `Element` {@link wikilinkTargetOf} walks. `HTMLElement` satisfies it. */
|
|
675
760
|
export interface ClosestElement {
|
|
676
761
|
getAttribute(name: string): string | null;
|
|
677
762
|
readonly parentElement: ClosestElement | null;
|
|
678
763
|
}
|
|
679
764
|
|
|
765
|
+
/** The task index carried by a rendered checkbox, if the target is one. */
|
|
766
|
+
export function taskCheckboxIndexOf(element: ClosestElement | null): number | null {
|
|
767
|
+
const raw = element?.getAttribute(TASK_CHECKBOX_ATTR);
|
|
768
|
+
if (raw === null || raw === undefined || !/^(?:0|[1-9]\d*)$/.test(raw)) return null;
|
|
769
|
+
return Number(raw);
|
|
770
|
+
}
|
|
771
|
+
|
|
680
772
|
/**
|
|
681
773
|
* Depth limit for the ancestor walk.
|
|
682
774
|
*
|
|
@@ -747,11 +839,8 @@ export const CREATED_WORD = "created";
|
|
|
747
839
|
|
|
748
840
|
// --- the editor -------------------------------------------------------------------------
|
|
749
841
|
|
|
750
|
-
/**
|
|
751
|
-
|
|
752
|
-
* {@link noteClickAction}); "Done", because leaving a clean editor cancels
|
|
753
|
-
* nothing.
|
|
754
|
-
*/
|
|
842
|
+
/** The editor's controls and prompts. */
|
|
843
|
+
export const EDIT_LABEL = "Edit";
|
|
755
844
|
export const DONE_LABEL = "Done";
|
|
756
845
|
export const SAVE_LABEL = "Save";
|
|
757
846
|
export const SAVING_LABEL = "Saving…";
|
|
@@ -767,18 +856,6 @@ export function draftDirty(draft: string, body: string): boolean {
|
|
|
767
856
|
return draft !== body;
|
|
768
857
|
}
|
|
769
858
|
|
|
770
|
-
/** What a click on the rendered body means. */
|
|
771
|
-
export type NoteClickAction = "ignore" | "navigate" | "edit";
|
|
772
|
-
|
|
773
|
-
/**
|
|
774
|
-
* Resolve a click on the prose. Selection wins (a drag to copy ends in a
|
|
775
|
-
* click, and 865da37 is that bug), then wikilinks, then editing.
|
|
776
|
-
*/
|
|
777
|
-
export function noteClickAction(hasSelection: boolean, wikilinkTarget: string | null): NoteClickAction {
|
|
778
|
-
if (hasSelection) return "ignore";
|
|
779
|
-
return wikilinkTarget === null ? "edit" : "navigate";
|
|
780
|
-
}
|
|
781
|
-
|
|
782
859
|
/**
|
|
783
860
|
* Whether a save's reply still belongs to the editor that issued it. A
|
|
784
861
|
* counter, not the slug: reopening the same note is a new session too.
|
|
@@ -441,11 +441,6 @@ body{font-size:var(--weave-px-base)}
|
|
|
441
441
|
chrome's 1.6: the extra leading is what a page of running text needs that a
|
|
442
442
|
status bar does not. Code stays mono deliberately. */
|
|
443
443
|
.weave-note-body{padding:12px var(--weave-note-gutter) 28px;font-size:var(--weave-px-body);line-height:1.7;color:var(--weave-fg)}
|
|
444
|
-
/* The prose is the edit affordance: a caret and a faint wash, not a border.
|
|
445
|
-
This column is read far more often than edited. */
|
|
446
|
-
.weave-note-body{cursor:text}
|
|
447
|
-
.weave-note-body:hover{background:var(--weave-panel)}
|
|
448
|
-
@media (prefers-reduced-motion: no-preference){.weave-note-body{transition:background 120ms ease}}
|
|
449
444
|
.weave-note-body>*:first-child{margin-top:0}
|
|
450
445
|
.weave-note-body h1,.weave-note-body h2,.weave-note-body h3,
|
|
451
446
|
.weave-note-body h4,.weave-note-body h5,.weave-note-body h6{
|
|
@@ -462,6 +457,8 @@ body{font-size:var(--weave-px-base)}
|
|
|
462
457
|
.weave-note-body p,.weave-note-body ul,.weave-note-body ol,.weave-note-body blockquote{margin:0 0 12px}
|
|
463
458
|
.weave-note-body ul,.weave-note-body ol{padding-left:20px}
|
|
464
459
|
.weave-note-body li{margin:4px 0}
|
|
460
|
+
.weave-note-body input[type="checkbox"]{accent-color:var(--weave-accent);cursor:pointer}
|
|
461
|
+
.weave-note-body input[type="checkbox"]:disabled{cursor:wait}
|
|
465
462
|
.weave-note-body blockquote{
|
|
466
463
|
padding-left:10px;border-left:2px solid var(--weave-line-strong);color:var(--weave-dim);
|
|
467
464
|
}
|