latex-stickies 1.4.1 → 1.4.2
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/src/editor/entry.js +6 -2
- package/src/renderer/live-editor.js +223 -21
- package/src/renderer/note.css +61 -12
- package/src/renderer/vendor/codemirror.js +17 -17
package/package.json
CHANGED
package/src/editor/entry.js
CHANGED
|
@@ -10,12 +10,15 @@
|
|
|
10
10
|
* @codemirror/language-data, which loads grammars with dynamic import -- that
|
|
11
11
|
* would leave the bundle with chunks it cannot fetch over file://.
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
EditorState, EditorSelection, StateField, StateEffect, RangeSetBuilder, Prec,
|
|
15
|
+
} from '@codemirror/state';
|
|
14
16
|
import {
|
|
15
17
|
EditorView, keymap, Decoration, WidgetType, ViewPlugin,
|
|
16
18
|
} from '@codemirror/view';
|
|
17
19
|
import {
|
|
18
20
|
defaultKeymap, history, historyKeymap, indentWithTab,
|
|
21
|
+
cursorLineUp, cursorLineDown, selectLineUp, selectLineDown,
|
|
19
22
|
} from '@codemirror/commands';
|
|
20
23
|
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
|
|
21
24
|
import {
|
|
@@ -63,9 +66,10 @@ const codeLanguages = [
|
|
|
63
66
|
];
|
|
64
67
|
|
|
65
68
|
window.CM = {
|
|
66
|
-
EditorState, StateField, StateEffect, RangeSetBuilder, Prec,
|
|
69
|
+
EditorState, EditorSelection, StateField, StateEffect, RangeSetBuilder, Prec,
|
|
67
70
|
EditorView, keymap, Decoration, WidgetType, ViewPlugin,
|
|
68
71
|
defaultKeymap, history, historyKeymap, indentWithTab,
|
|
72
|
+
cursorLineUp, cursorLineDown, selectLineUp, selectLineDown,
|
|
69
73
|
markdown, markdownLanguage,
|
|
70
74
|
syntaxTree, HighlightStyle, syntaxHighlighting, defaultHighlightStyle,
|
|
71
75
|
tags, codeLanguages,
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
const {
|
|
18
|
-
EditorState,
|
|
18
|
+
EditorState, EditorSelection, StateField, StateEffect, EditorView, Decoration,
|
|
19
|
+
WidgetType, ViewPlugin, keymap, Prec,
|
|
19
20
|
defaultKeymap, history, historyKeymap, indentWithTab,
|
|
21
|
+
cursorLineUp, cursorLineDown, selectLineUp, selectLineDown,
|
|
20
22
|
markdown, markdownLanguage, codeLanguages,
|
|
21
23
|
syntaxTree, HighlightStyle, syntaxHighlighting, defaultHighlightStyle, tags,
|
|
22
24
|
search, searchKeymap, highlightSelectionMatches,
|
|
@@ -126,18 +128,19 @@ class BulletWidget extends WidgetType {
|
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
class CopyButtonWidget extends WidgetType {
|
|
129
|
-
constructor(code) {
|
|
131
|
+
constructor(code, visible) {
|
|
130
132
|
super();
|
|
131
133
|
this.code = code;
|
|
134
|
+
this.visible = visible;
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
eq(other) {
|
|
135
|
-
return other.code === this.code;
|
|
138
|
+
return other.code === this.code && other.visible === this.visible;
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
toDOM() {
|
|
139
142
|
const button = document.createElement('span');
|
|
140
|
-
button.className = 'cm-copy';
|
|
143
|
+
button.className = this.visible ? 'cm-copy cm-copy-open' : 'cm-copy';
|
|
141
144
|
button.textContent = 'Copy';
|
|
142
145
|
button.title = 'Copy this block';
|
|
143
146
|
button.addEventListener('mousedown', (e) => {
|
|
@@ -291,6 +294,32 @@ class RuleWidget extends WidgetType {
|
|
|
291
294
|
|
|
292
295
|
const HIDE = Decoration.replace({});
|
|
293
296
|
|
|
297
|
+
/**
|
|
298
|
+
* A syntax marker that is hidden by width rather than removed.
|
|
299
|
+
*
|
|
300
|
+
* Replacing "## " takes it out of the rendered line entirely, so the caret
|
|
301
|
+
* jumps over it and the heading pops between two widths as you arrive. Kept as
|
|
302
|
+
* a mark at font-size 0 the characters stay in the line box and can simply
|
|
303
|
+
* slide back in. Only their width animates -- the heading text beside them
|
|
304
|
+
* already sets the line height -- so CodeMirror's vertical measurements, which
|
|
305
|
+
* drive cursor placement and scrolling, are never in motion.
|
|
306
|
+
*
|
|
307
|
+
* The class never varies. A decoration with a different class is a different
|
|
308
|
+
* decoration, and CodeMirror rebuilds the span rather than restyling it --
|
|
309
|
+
* a brand new element has no previous width to animate from, so the marker
|
|
310
|
+
* snapped back into place. The reveal is `markerReveal` below, which sets a
|
|
311
|
+
* class on the existing element and leaves the decoration alone.
|
|
312
|
+
*/
|
|
313
|
+
const MARKER = Decoration.mark({ class: 'cm-md-marker' });
|
|
314
|
+
|
|
315
|
+
/** Elements whose markers are shown while the caret is inside them. */
|
|
316
|
+
const REVEAL = new Set([
|
|
317
|
+
'Emphasis', 'StrongEmphasis', 'Strikethrough', 'InlineCode', 'FencedCode',
|
|
318
|
+
'Blockquote', 'ListItem', 'Link',
|
|
319
|
+
'ATXHeading1', 'ATXHeading2', 'ATXHeading3',
|
|
320
|
+
'ATXHeading4', 'ATXHeading5', 'ATXHeading6',
|
|
321
|
+
]);
|
|
322
|
+
|
|
294
323
|
/** Line classes for blocks that keep their markdown but are styled as blocks. */
|
|
295
324
|
const LINE_CLASS = {
|
|
296
325
|
Table: 'cm-md-table',
|
|
@@ -310,6 +339,47 @@ const MARKS = new Set([
|
|
|
310
339
|
'QuoteMark', 'StrikethroughMark',
|
|
311
340
|
]);
|
|
312
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Which fenced block the pointer is over, as its start position, or -1.
|
|
344
|
+
*
|
|
345
|
+
* Each line is its own element with nothing wrapping the block, so there is no
|
|
346
|
+
* element to hang a CSS :hover on -- hovering has to be worked out from the
|
|
347
|
+
* pointer's document position instead.
|
|
348
|
+
*/
|
|
349
|
+
const setHoveredFence = StateEffect.define();
|
|
350
|
+
|
|
351
|
+
const hoveredFence = StateField.define({
|
|
352
|
+
create: () => -1,
|
|
353
|
+
update(value, tr) {
|
|
354
|
+
for (const effect of tr.effects) if (effect.is(setHoveredFence)) return effect.value;
|
|
355
|
+
return tr.docChanged ? -1 : value;
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
/** Reports the fenced block under the pointer, dispatching only on a change. */
|
|
360
|
+
const fenceHover = EditorView.domEventHandlers({
|
|
361
|
+
mousemove(event, view) {
|
|
362
|
+
const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });
|
|
363
|
+
let fence = -1;
|
|
364
|
+
if (pos !== null) {
|
|
365
|
+
for (let node = syntaxTree(view.state).resolveInner(pos, 1); node; node = node.parent) {
|
|
366
|
+
if (node.name === 'FencedCode') {
|
|
367
|
+
fence = node.from;
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (fence !== view.state.field(hoveredFence)) {
|
|
373
|
+
view.dispatch({ effects: setHoveredFence.of(fence) });
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
mouseleave(_event, view) {
|
|
377
|
+
if (view.state.field(hoveredFence) !== -1) {
|
|
378
|
+
view.dispatch({ effects: setHoveredFence.of(-1) });
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
|
|
313
383
|
function buildDecorations(state) {
|
|
314
384
|
const sel = state.selection.main;
|
|
315
385
|
const ranges = [];
|
|
@@ -389,7 +459,10 @@ function buildDecorations(state) {
|
|
|
389
459
|
ranges.push({
|
|
390
460
|
from: openLine.to,
|
|
391
461
|
to: openLine.to,
|
|
392
|
-
value: Decoration.widget({
|
|
462
|
+
value: Decoration.widget({
|
|
463
|
+
widget: new CopyButtonWidget(body, state.field(hoveredFence, false) === node.from),
|
|
464
|
+
side: 1,
|
|
465
|
+
}),
|
|
393
466
|
});
|
|
394
467
|
}
|
|
395
468
|
}
|
|
@@ -444,13 +517,10 @@ function buildDecorations(state) {
|
|
|
444
517
|
return;
|
|
445
518
|
}
|
|
446
519
|
|
|
447
|
-
// The ``` runs are punctuation, not content.
|
|
448
|
-
// is
|
|
520
|
+
// The ``` runs are punctuation, not content. They stay hidden until the
|
|
521
|
+
// caret is on their line; the language name is the block's label.
|
|
449
522
|
if (node.name === 'CodeMark' && node.to - node.from >= 3) {
|
|
450
|
-
|
|
451
|
-
if (!cursorInside(fence.from, fence.to)) {
|
|
452
|
-
ranges.push({ from: node.from, to: node.to, value: HIDE });
|
|
453
|
-
}
|
|
523
|
+
ranges.push({ from: node.from, to: node.to, value: MARKER });
|
|
454
524
|
return;
|
|
455
525
|
}
|
|
456
526
|
|
|
@@ -493,7 +563,7 @@ function buildDecorations(state) {
|
|
|
493
563
|
ranges.push({
|
|
494
564
|
from: node.from,
|
|
495
565
|
to: node.to,
|
|
496
|
-
value: isTask ?
|
|
566
|
+
value: isTask ? MARKER : Decoration.replace({ widget: new BulletWidget() }),
|
|
497
567
|
});
|
|
498
568
|
return;
|
|
499
569
|
}
|
|
@@ -502,24 +572,18 @@ function buildDecorations(state) {
|
|
|
502
572
|
// Inline backticks only: hiding a fence would strand its language
|
|
503
573
|
// label and the block's boundaries.
|
|
504
574
|
if (node.to - node.from > 2) return;
|
|
505
|
-
|
|
506
|
-
if (!cursorInside(parent.from, parent.to)) {
|
|
507
|
-
ranges.push({ from: node.from, to: node.to, value: HIDE });
|
|
508
|
-
}
|
|
575
|
+
ranges.push({ from: node.from, to: node.to, value: MARKER });
|
|
509
576
|
return;
|
|
510
577
|
}
|
|
511
578
|
|
|
512
579
|
if (MARKS.has(node.name)) {
|
|
513
|
-
const parent = node.node.parent || node;
|
|
514
|
-
if (cursorInside(parent.from, parent.to)) return;
|
|
515
|
-
|
|
516
580
|
// Take the space that follows a heading or quote marker with it.
|
|
517
581
|
// Hiding "#" alone leaves the title indented by one space.
|
|
518
582
|
let to = node.to;
|
|
519
583
|
if (node.name === 'HeaderMark' || node.name === 'QuoteMark') {
|
|
520
584
|
while (state.doc.sliceString(to, to + 1) === ' ') to += 1;
|
|
521
585
|
}
|
|
522
|
-
ranges.push({ from: node.from, to, value:
|
|
586
|
+
ranges.push({ from: node.from, to, value: MARKER });
|
|
523
587
|
}
|
|
524
588
|
},
|
|
525
589
|
});
|
|
@@ -529,6 +593,63 @@ function buildDecorations(state) {
|
|
|
529
593
|
return Decoration.set(ranges.map((r) => r.value.range(r.from, r.to)), true);
|
|
530
594
|
}
|
|
531
595
|
|
|
596
|
+
/**
|
|
597
|
+
* Shows the markers of whichever element the caret is in.
|
|
598
|
+
*
|
|
599
|
+
* This is a plugin rather than a decoration because the reveal has to leave
|
|
600
|
+
* the DOM node alone: change the decoration and CodeMirror builds a new span,
|
|
601
|
+
* which has no width to animate from and snaps. Setting a class on the element
|
|
602
|
+
* already there is what lets it slide.
|
|
603
|
+
*
|
|
604
|
+
* It also asks for a re-measure when the slide finishes. The caret is
|
|
605
|
+
* positioned once, at the start of the transition, so without this it sits a
|
|
606
|
+
* few pixels off the text until the next keystroke -- which reads as a click
|
|
607
|
+
* that did not take.
|
|
608
|
+
*/
|
|
609
|
+
const markerReveal = ViewPlugin.fromClass(class {
|
|
610
|
+
constructor(view) {
|
|
611
|
+
this.view = view;
|
|
612
|
+
this.onEnd = (event) => {
|
|
613
|
+
if (event.target.classList.contains('cm-md-marker')) view.requestMeasure();
|
|
614
|
+
};
|
|
615
|
+
view.contentDOM.addEventListener('transitionend', this.onEnd);
|
|
616
|
+
this.sync();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
update(update) {
|
|
620
|
+
if (update.docChanged || update.selectionSet || update.viewportChanged) this.sync();
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
destroy() {
|
|
624
|
+
this.view.contentDOM.removeEventListener('transitionend', this.onEnd);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
sync() {
|
|
628
|
+
const { view } = this;
|
|
629
|
+
const sel = view.state.selection.main;
|
|
630
|
+
let from = -1;
|
|
631
|
+
let to = -1;
|
|
632
|
+
if (!snapshotMode) {
|
|
633
|
+
for (let node = syntaxTree(view.state).resolveInner(sel.from, 1); node; node = node.parent) {
|
|
634
|
+
if (REVEAL.has(node.name) && sel.to <= node.to) {
|
|
635
|
+
from = node.from;
|
|
636
|
+
to = node.to;
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
for (const el of view.contentDOM.querySelectorAll('.cm-md-marker')) {
|
|
642
|
+
let pos;
|
|
643
|
+
try {
|
|
644
|
+
pos = view.posAtDOM(el);
|
|
645
|
+
} catch (_) {
|
|
646
|
+
continue; // mid-update, and the next sync will catch it
|
|
647
|
+
}
|
|
648
|
+
el.classList.toggle('cm-md-marker-open', pos >= from && pos < to);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
|
|
532
653
|
/**
|
|
533
654
|
* Decorations live in a state field rather than a view plugin. Display maths
|
|
534
655
|
* replaces a whole line, and CodeMirror only accepts block decorations from a
|
|
@@ -537,7 +658,10 @@ function buildDecorations(state) {
|
|
|
537
658
|
const livePreview = StateField.define({
|
|
538
659
|
create: (state) => buildDecorations(state),
|
|
539
660
|
update(deco, tr) {
|
|
540
|
-
|
|
661
|
+
const hovered = tr.effects.some((effect) => effect.is(setHoveredFence));
|
|
662
|
+
if (tr.docChanged || tr.selection || hovered || snapshotDirty) {
|
|
663
|
+
return buildDecorations(tr.state);
|
|
664
|
+
}
|
|
541
665
|
return deco.map(tr.changes);
|
|
542
666
|
},
|
|
543
667
|
provide: (field) => EditorView.decorations.from(field),
|
|
@@ -586,6 +710,80 @@ function wrapCommand(key) {
|
|
|
586
710
|
};
|
|
587
711
|
}
|
|
588
712
|
|
|
713
|
+
/**
|
|
714
|
+
* ArrowUp and ArrowDown that cannot skip a line.
|
|
715
|
+
*
|
|
716
|
+
* CodeMirror moves the caret by screen geometry, and its measurements assume
|
|
717
|
+
* one text height for the whole document -- a single number, measured once
|
|
718
|
+
* from one short line. This note is not like that: code is smaller than the
|
|
719
|
+
* prose, headings are bigger. When a probe lands in a line's padding rather
|
|
720
|
+
* than on its glyphs, `posAtCoords` does not clamp into that line, it moves to
|
|
721
|
+
* the top of the block and tries again, so one press could clear a whole code
|
|
722
|
+
* block, the heading above it and the table above that.
|
|
723
|
+
*
|
|
724
|
+
* Only vertical motion hits this: it is the one caller that passes a scan
|
|
725
|
+
* direction. The public posAtCoords, used below to find the column, does not.
|
|
726
|
+
*
|
|
727
|
+
* So the built-in command still decides *whether* to move -- it knows about
|
|
728
|
+
* wrapped lines, which this must not break -- and this only pulls the caret
|
|
729
|
+
* back when it has flown past a line it could have landed on.
|
|
730
|
+
*
|
|
731
|
+
* Extending a selection goes through the same geometry, so it gets the same
|
|
732
|
+
* treatment: only the head moves, the anchor is left where the selection
|
|
733
|
+
* started.
|
|
734
|
+
*/
|
|
735
|
+
function verticalStep(forward, base, extend) {
|
|
736
|
+
return (view) => {
|
|
737
|
+
const { doc } = view.state;
|
|
738
|
+
const start = view.state.selection.main;
|
|
739
|
+
const startLine = doc.lineAt(start.head);
|
|
740
|
+
if (!base(view)) return false;
|
|
741
|
+
|
|
742
|
+
const landed = view.state.selection.main;
|
|
743
|
+
const landedNumber = doc.lineAt(landed.head).number;
|
|
744
|
+
const moved = forward ? landedNumber - startLine.number : startLine.number - landedNumber;
|
|
745
|
+
// 0 is a step within a wrapped line, 1 is the next line: both are right.
|
|
746
|
+
if (moved <= 1) return true;
|
|
747
|
+
|
|
748
|
+
const next = doc.line(forward ? startLine.number + 1 : startLine.number - 1);
|
|
749
|
+
|
|
750
|
+
// A widget standing in for whole lines has nowhere to put a caret, so
|
|
751
|
+
// flying over a table is the correct answer, not a bug to undo.
|
|
752
|
+
const block = view.lineBlockAt(next.from);
|
|
753
|
+
if (block.from !== next.from || block.to !== next.to) return true;
|
|
754
|
+
|
|
755
|
+
// Coming up, the caret belongs on the line's last wrapped row.
|
|
756
|
+
const edge = forward ? next.from : next.to;
|
|
757
|
+
const goal = landed.goalColumn ?? start.goalColumn;
|
|
758
|
+
let pos = null;
|
|
759
|
+
if (goal != null) {
|
|
760
|
+
const coords = view.coordsAtPos(edge);
|
|
761
|
+
if (coords) {
|
|
762
|
+
const left = view.contentDOM.getBoundingClientRect().left;
|
|
763
|
+
pos = view.posAtCoords({ x: left + goal, y: (coords.top + coords.bottom) / 2 }, false);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (pos == null || doc.lineAt(pos).number !== next.number) {
|
|
767
|
+
pos = Math.min(next.to, next.from + (start.head - startLine.from));
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
view.dispatch({
|
|
771
|
+
selection: extend
|
|
772
|
+
? EditorSelection.range(start.anchor, pos, goal ?? undefined)
|
|
773
|
+
: EditorSelection.cursor(pos, undefined, undefined, goal ?? undefined),
|
|
774
|
+
scrollIntoView: true,
|
|
775
|
+
});
|
|
776
|
+
return true;
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const verticalKeymap = [
|
|
781
|
+
{ key: 'ArrowUp', run: verticalStep(false, cursorLineUp), preventDefault: true },
|
|
782
|
+
{ key: 'ArrowDown', run: verticalStep(true, cursorLineDown), preventDefault: true },
|
|
783
|
+
{ key: 'Shift-ArrowUp', run: verticalStep(false, selectLineUp, true), preventDefault: true },
|
|
784
|
+
{ key: 'Shift-ArrowDown', run: verticalStep(true, selectLineDown, true), preventDefault: true },
|
|
785
|
+
];
|
|
786
|
+
|
|
589
787
|
const shortcuts = [
|
|
590
788
|
{ key: 'Mod-b', run: wrapCommand('b') },
|
|
591
789
|
{ key: 'Mod-i', run: wrapCommand('i') },
|
|
@@ -605,6 +803,7 @@ function createLiveEditor({ parent, doc, onChange }) {
|
|
|
605
803
|
history(),
|
|
606
804
|
// Ahead of the defaults, so Mod-i and friends are not swallowed.
|
|
607
805
|
Prec.high(keymap.of(shortcuts)),
|
|
806
|
+
Prec.high(keymap.of(verticalKeymap)),
|
|
608
807
|
// Search ahead of the defaults: Cmd+F must open the panel rather than
|
|
609
808
|
// fall through to anything else bound to it.
|
|
610
809
|
Prec.high(keymap.of(searchKeymap)),
|
|
@@ -614,7 +813,10 @@ function createLiveEditor({ parent, doc, onChange }) {
|
|
|
614
813
|
markdown({ base: markdownLanguage, codeLanguages }),
|
|
615
814
|
Prec.high(syntaxHighlighting(markdownStyle)),
|
|
616
815
|
syntaxHighlighting(defaultHighlightStyle), // colours inside code fences
|
|
816
|
+
hoveredFence,
|
|
617
817
|
livePreview,
|
|
818
|
+
markerReveal,
|
|
819
|
+
fenceHover,
|
|
618
820
|
ghostCompletion(),
|
|
619
821
|
EditorView.lineWrapping,
|
|
620
822
|
EditorView.updateListener.of((update) => {
|
package/src/renderer/note.css
CHANGED
|
@@ -160,20 +160,32 @@ body.editing #tools > * { visibility: visible; }
|
|
|
160
160
|
opacity: 0.85;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/* Smaller text, but the same line box as prose. CodeMirror moves the caret
|
|
164
|
+
vertically by one default line height, so a line shorter than that gets
|
|
165
|
+
stepped over: arrowing up out of a code block landed above it, and clicking
|
|
166
|
+
a line put the caret on the one below. Dividing the prose line height by the
|
|
167
|
+
font size keeps the block looking the same and the geometry uniform. */
|
|
163
168
|
.cm-editor .cm-md-table {
|
|
164
169
|
font-family: ui-monospace, Menlo, monospace;
|
|
165
170
|
font-size: 0.9em;
|
|
171
|
+
line-height: calc(1.55 / 0.9);
|
|
166
172
|
background: rgba(0, 0, 0, 0.04);
|
|
167
173
|
}
|
|
168
174
|
|
|
169
175
|
.cm-editor .cm-md-code {
|
|
170
176
|
font-family: ui-monospace, Menlo, monospace;
|
|
171
177
|
font-size: 0.86em;
|
|
178
|
+
line-height: calc(1.55 / 0.86);
|
|
172
179
|
background: rgba(0, 0, 0, 0.07);
|
|
173
|
-
/*
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
180
|
+
/* The block reads as a panel with breathing room, without the tint running
|
|
181
|
+
into the window edge. Transparent borders rather than a margin: a margin
|
|
182
|
+
takes those 2px out of the line's box, and CodeMirror could then not hit
|
|
183
|
+
the line at that x -- clicking there did nothing, and arrowing up out of
|
|
184
|
+
a code block sailed over the whole block looking for something it could
|
|
185
|
+
land on. */
|
|
186
|
+
border-left: 2px solid transparent;
|
|
187
|
+
border-right: 2px solid transparent;
|
|
188
|
+
background-clip: padding-box;
|
|
177
189
|
padding-left: 8px;
|
|
178
190
|
padding-right: 8px;
|
|
179
191
|
}
|
|
@@ -196,6 +208,40 @@ body.editing #tools > * { visibility: visible; }
|
|
|
196
208
|
padding-bottom: 5px;
|
|
197
209
|
}
|
|
198
210
|
|
|
211
|
+
/* Syntax markers -- "## ", "**", the backticks -- are hidden by width, not
|
|
212
|
+
removed, so the caret still lands on them and they can slide back in when
|
|
213
|
+
it reaches their element.
|
|
214
|
+
|
|
215
|
+
font-size: 0 is the obvious way to do that and it breaks arrow keys. A span
|
|
216
|
+
with no font size has a rect with no *height*, so a line beginning with a
|
|
217
|
+
marker cannot be hit at any y, and moving up or down steps straight over it
|
|
218
|
+
-- the caret would skip every heading. Clipping a full-size box keeps the
|
|
219
|
+
height and takes the width, which is what the caret is measured against.
|
|
220
|
+
|
|
221
|
+
max-width rather than width because there is nothing to animate towards:
|
|
222
|
+
the open state has to be wide enough for the longest marker and let the
|
|
223
|
+
content stop it short. Only the width moves; the text beside a marker is
|
|
224
|
+
already at full size and owns the line height, so nothing CodeMirror
|
|
225
|
+
measures vertically changes mid-transition. Heading sizes deliberately snap
|
|
226
|
+
for the same reason.
|
|
227
|
+
|
|
228
|
+
The open class is put on by hand in live-editor.js, not by a decoration:
|
|
229
|
+
CodeMirror rebuilds a span whose decoration changed, and a new element has
|
|
230
|
+
no width to animate from. */
|
|
231
|
+
.cm-editor .cm-md-marker {
|
|
232
|
+
display: inline-block;
|
|
233
|
+
overflow: hidden;
|
|
234
|
+
white-space: pre;
|
|
235
|
+
vertical-align: bottom;
|
|
236
|
+
max-width: 0;
|
|
237
|
+
opacity: 0;
|
|
238
|
+
transition: max-width 0.12s ease, opacity 0.12s ease;
|
|
239
|
+
}
|
|
240
|
+
.cm-editor .cm-md-marker-open {
|
|
241
|
+
max-width: 4em;
|
|
242
|
+
opacity: 0.35;
|
|
243
|
+
}
|
|
244
|
+
|
|
199
245
|
/* Sits at the end of a block's opening fence line, beside the language name. */
|
|
200
246
|
.cm-editor .cm-copy {
|
|
201
247
|
float: right;
|
|
@@ -210,14 +256,17 @@ body.editing #tools > * { visibility: visible; }
|
|
|
210
256
|
cursor: default;
|
|
211
257
|
transition: opacity 0.12s ease;
|
|
212
258
|
|
|
213
|
-
/*
|
|
214
|
-
element with nothing wrapping the block, so there is
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
259
|
+
/* Hidden until the pointer is anywhere in the block. CodeMirror renders
|
|
260
|
+
each line as its own element with nothing wrapping the block, so there is
|
|
261
|
+
no element to hang a :hover on -- live-editor.js works the hovered block
|
|
262
|
+
out from the pointer's document position and adds .cm-copy-open. Keying
|
|
263
|
+
it to the fence line alone left the button findable only by someone who
|
|
264
|
+
already knew it was there. */
|
|
265
|
+
opacity: 0;
|
|
266
|
+
pointer-events: none;
|
|
267
|
+
}
|
|
268
|
+
.cm-editor .cm-copy-open { opacity: 0.55; pointer-events: auto; }
|
|
269
|
+
.cm-editor .cm-copy-open:hover { opacity: 1; background: rgba(0, 0, 0, 0.16); }
|
|
221
270
|
.cm-editor .cm-copy.done { opacity: 1; background: rgba(47, 107, 74, 0.22); }
|
|
222
271
|
|
|
223
272
|
.cm-editor .cm-table {
|