latex-stickies 1.4.2 → 1.4.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/README.md +1 -0
- package/package.json +4 -1
- package/scripts/brand-electron.js +8 -0
- package/src/editor/entry.js +4 -2
- package/src/main.js +40 -3
- package/src/renderer/live-editor.js +68 -8
- package/src/renderer/note.css +33 -4
- package/src/renderer/vendor/codemirror.js +18 -18
package/README.md
CHANGED
|
@@ -169,6 +169,7 @@ node scripts/render-check.js # the first-run note renders correctly
|
|
|
169
169
|
node scripts/ghost-check.js # autocomplete suggests, and Tab accepts
|
|
170
170
|
node scripts/snapshot-check.js # a long note is captured whole
|
|
171
171
|
node scripts/conflict-check.js # the changed-on-disk banner behaves
|
|
172
|
+
node scripts/denied-check.js # a notes folder it may not read is reported
|
|
172
173
|
node scripts/verify-install.js # the install path, end to end (macOS)
|
|
173
174
|
```
|
|
174
175
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "latex-stickies",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.4",
|
|
4
4
|
"description": "Sticky notes for your desktop that render LaTeX and Markdown.",
|
|
5
5
|
"main": "src/main.js",
|
|
6
6
|
"scripts": {
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
],
|
|
26
26
|
"mac": {
|
|
27
27
|
"category": "public.app-category.productivity",
|
|
28
|
+
"extendInfo": {
|
|
29
|
+
"NSDocumentsFolderUsageDescription": "LaTeX Stickies keeps your notes as Markdown files in Documents, so you can read and edit them with any other app."
|
|
30
|
+
},
|
|
28
31
|
"target": [
|
|
29
32
|
"dmg",
|
|
30
33
|
"zip"
|
|
@@ -94,6 +94,10 @@ function isCurrent(version) {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/** Shown in the macOS prompt asking to let the app read the notes folder. */
|
|
98
|
+
const DOCUMENTS_REASON = 'LaTeX Stickies keeps your notes as Markdown files in '
|
|
99
|
+
+ 'Documents, so you can read and edit them with any other app.';
|
|
100
|
+
|
|
97
101
|
function setPlist(plist, key, value) {
|
|
98
102
|
try {
|
|
99
103
|
run('plutil', ['-replace', key, '-string', value, plist]);
|
|
@@ -126,6 +130,10 @@ function build(app, version) {
|
|
|
126
130
|
setPlist(plist, 'CFBundleDisplayName', NAME);
|
|
127
131
|
setPlist(plist, 'CFBundleIdentifier', APP_ID);
|
|
128
132
|
setPlist(plist, 'CFBundleExecutable', NAME);
|
|
133
|
+
// Without this key macOS denies the notes folder without ever asking. The
|
|
134
|
+
// app then starts, reads nothing and shows no window, which looks exactly
|
|
135
|
+
// like a launch that failed.
|
|
136
|
+
setPlist(plist, 'NSDocumentsFolderUsageDescription', DOCUMENTS_REASON);
|
|
129
137
|
|
|
130
138
|
const from = path.join(TARGET, 'Contents', 'MacOS', 'Electron');
|
|
131
139
|
const to = path.join(TARGET, 'Contents', 'MacOS', NAME);
|
package/src/editor/entry.js
CHANGED
|
@@ -20,7 +20,9 @@ import {
|
|
|
20
20
|
defaultKeymap, history, historyKeymap, indentWithTab,
|
|
21
21
|
cursorLineUp, cursorLineDown, selectLineUp, selectLineDown,
|
|
22
22
|
} from '@codemirror/commands';
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
markdown, markdownLanguage, insertNewlineContinueMarkup,
|
|
25
|
+
} from '@codemirror/lang-markdown';
|
|
24
26
|
import {
|
|
25
27
|
syntaxTree, HighlightStyle, syntaxHighlighting, defaultHighlightStyle,
|
|
26
28
|
LanguageDescription,
|
|
@@ -70,7 +72,7 @@ window.CM = {
|
|
|
70
72
|
EditorView, keymap, Decoration, WidgetType, ViewPlugin,
|
|
71
73
|
defaultKeymap, history, historyKeymap, indentWithTab,
|
|
72
74
|
cursorLineUp, cursorLineDown, selectLineUp, selectLineDown,
|
|
73
|
-
markdown, markdownLanguage,
|
|
75
|
+
markdown, markdownLanguage, insertNewlineContinueMarkup,
|
|
74
76
|
syntaxTree, HighlightStyle, syntaxHighlighting, defaultHighlightStyle,
|
|
75
77
|
tags, codeLanguages,
|
|
76
78
|
search, searchKeymap, highlightSelectionMatches, openSearchPanel,
|
package/src/main.js
CHANGED
|
@@ -527,6 +527,37 @@ function watchNotesFolder() {
|
|
|
527
527
|
});
|
|
528
528
|
}
|
|
529
529
|
|
|
530
|
+
/**
|
|
531
|
+
* macOS can refuse the notes folder outright, and quietly.
|
|
532
|
+
*
|
|
533
|
+
* Documents is protected, and permission is tied to the app's signature -- so
|
|
534
|
+
* a rebuilt copy is a different app to the system and starts again with no
|
|
535
|
+
* access. Reading the folder then fails, nothing is restored and no window
|
|
536
|
+
* opens, which is indistinguishable from a launch that crashed. Say what
|
|
537
|
+
* happened instead, and offer the settings pane that fixes it.
|
|
538
|
+
*/
|
|
539
|
+
function reportNoAccess(err) {
|
|
540
|
+
// Printed as well as shown: the dialog is the answer for the person at the
|
|
541
|
+
// screen, this line is what a harness or a bug report can see.
|
|
542
|
+
console.error(`notes folder refused: ${err.code} ${store.DIR}`);
|
|
543
|
+
const settings = 'x-apple.systempreferences:com.apple.preference.security'
|
|
544
|
+
+ '?Privacy_DocumentsFolder';
|
|
545
|
+
const choice = dialog.showMessageBoxSync({
|
|
546
|
+
type: 'error',
|
|
547
|
+
message: 'LaTeX Stickies cannot read your notes',
|
|
548
|
+
detail: `macOS is not letting it open ${store.DIR}.\n\n`
|
|
549
|
+
+ 'Allow it under Privacy & Security, in Files and Folders, then open '
|
|
550
|
+
+ `the app again.\n\n(${err.code}: ${err.message})`,
|
|
551
|
+
buttons: ['Open Privacy Settings', 'Quit'],
|
|
552
|
+
defaultId: 0,
|
|
553
|
+
});
|
|
554
|
+
if (choice === 0 && process.platform === 'darwin') shell.openExternal(settings);
|
|
555
|
+
app.quit();
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/** True when the filesystem said no, rather than the folder being absent. */
|
|
559
|
+
const denied = (err) => err && (err.code === 'EPERM' || err.code === 'EACCES');
|
|
560
|
+
|
|
530
561
|
app.whenReady().then(async () => {
|
|
531
562
|
// Run from npm there is no .app bundle of our own to carry the icon, so
|
|
532
563
|
// macOS would show the generic Electron atom in the Dock. Set it explicitly.
|
|
@@ -546,9 +577,15 @@ app.whenReady().then(async () => {
|
|
|
546
577
|
}
|
|
547
578
|
}
|
|
548
579
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
580
|
+
try {
|
|
581
|
+
await buildMenu();
|
|
582
|
+
restoreNotes();
|
|
583
|
+
watchNotesFolder();
|
|
584
|
+
} catch (err) {
|
|
585
|
+
if (!denied(err)) throw err;
|
|
586
|
+
reportNoAccess(err);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
552
589
|
|
|
553
590
|
if (SMOKE_CLOSE_MS) {
|
|
554
591
|
setTimeout(() => {
|
|
@@ -19,7 +19,7 @@ const {
|
|
|
19
19
|
WidgetType, ViewPlugin, keymap, Prec,
|
|
20
20
|
defaultKeymap, history, historyKeymap, indentWithTab,
|
|
21
21
|
cursorLineUp, cursorLineDown, selectLineUp, selectLineDown,
|
|
22
|
-
markdown, markdownLanguage, codeLanguages,
|
|
22
|
+
markdown, markdownLanguage, codeLanguages, insertNewlineContinueMarkup,
|
|
23
23
|
syntaxTree, HighlightStyle, syntaxHighlighting, defaultHighlightStyle, tags,
|
|
24
24
|
search, searchKeymap, highlightSelectionMatches,
|
|
25
25
|
} = window.CM;
|
|
@@ -312,6 +312,8 @@ const HIDE = Decoration.replace({});
|
|
|
312
312
|
*/
|
|
313
313
|
const MARKER = Decoration.mark({ class: 'cm-md-marker' });
|
|
314
314
|
|
|
315
|
+
|
|
316
|
+
|
|
315
317
|
/** Elements whose markers are shown while the caret is inside them. */
|
|
316
318
|
const REVEAL = new Set([
|
|
317
319
|
'Emphasis', 'StrongEmphasis', 'Strikethrough', 'InlineCode', 'FencedCode',
|
|
@@ -445,6 +447,23 @@ function buildDecorations(state) {
|
|
|
445
447
|
const first = state.doc.lineAt(node.from).number;
|
|
446
448
|
const last = state.doc.lineAt(node.to).number;
|
|
447
449
|
for (let n = first; n <= last; n++) addLine(state.doc.line(n).from, cls);
|
|
450
|
+
|
|
451
|
+
// A quote inside a quote is a quote inside a quote, and drawing every
|
|
452
|
+
// level the same made ">" and ">>" identical on screen. The outer
|
|
453
|
+
// node is entered first, so the deeper class lands only on the lines
|
|
454
|
+
// that are actually deeper. Capped because past three the indent eats
|
|
455
|
+
// the width of a sticky note.
|
|
456
|
+
if (node.name === 'Blockquote') {
|
|
457
|
+
let depth = 0;
|
|
458
|
+
for (let up = node.node; up; up = up.parent) {
|
|
459
|
+
if (up.name === 'Blockquote') depth += 1;
|
|
460
|
+
}
|
|
461
|
+
if (depth > 1) {
|
|
462
|
+
const level = `cm-md-quote-${Math.min(depth, 3)}`;
|
|
463
|
+
for (let n = first; n <= last; n++) addLine(state.doc.line(n).from, level);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
448
467
|
if (node.name === 'FencedCode') {
|
|
449
468
|
addLine(state.doc.line(first).from, 'cm-md-code-first');
|
|
450
469
|
addLine(state.doc.line(last).from, 'cm-md-code-last');
|
|
@@ -616,8 +635,13 @@ const markerReveal = ViewPlugin.fromClass(class {
|
|
|
616
635
|
this.sync();
|
|
617
636
|
}
|
|
618
637
|
|
|
619
|
-
update(
|
|
620
|
-
|
|
638
|
+
update() {
|
|
639
|
+
// Every update, not only the ones that moved the caret. The parser runs
|
|
640
|
+
// behind the keystroke, so the first "#" of a heading arrives as a plain
|
|
641
|
+
// character and becomes a marker on a later update that changed neither
|
|
642
|
+
// the document nor the selection. Skipping those left the reveal one
|
|
643
|
+
// keystroke behind: a heading stayed hidden while it was being typed.
|
|
644
|
+
this.sync();
|
|
621
645
|
}
|
|
622
646
|
|
|
623
647
|
destroy() {
|
|
@@ -630,12 +654,21 @@ const markerReveal = ViewPlugin.fromClass(class {
|
|
|
630
654
|
let from = -1;
|
|
631
655
|
let to = -1;
|
|
632
656
|
if (!snapshotMode) {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
657
|
+
const tree = syntaxTree(view.state);
|
|
658
|
+
// Both sides of the caret, and the side before it first. Typing "###"
|
|
659
|
+
// leaves the caret at the end of the line, where the node *starting*
|
|
660
|
+
// here is the document, not the heading -- so looking forward only, a
|
|
661
|
+
// heading stayed hidden the whole time you were typing it.
|
|
662
|
+
const enclosing = (side) => {
|
|
663
|
+
for (let node = tree.resolveInner(sel.from, side); node; node = node.parent) {
|
|
664
|
+
if (REVEAL.has(node.name) && sel.to <= node.to) return node;
|
|
638
665
|
}
|
|
666
|
+
return null;
|
|
667
|
+
};
|
|
668
|
+
const node = enclosing(-1) || enclosing(1);
|
|
669
|
+
if (node) {
|
|
670
|
+
from = node.from;
|
|
671
|
+
to = node.to;
|
|
639
672
|
}
|
|
640
673
|
}
|
|
641
674
|
for (const el of view.contentDOM.querySelectorAll('.cm-md-marker')) {
|
|
@@ -669,9 +702,24 @@ const livePreview = StateField.define({
|
|
|
669
702
|
|
|
670
703
|
/* ---------- how markdown reads ---------- */
|
|
671
704
|
|
|
705
|
+
/**
|
|
706
|
+
* Markers are punctuation, not prose, and should not compete with the text
|
|
707
|
+
* they mark up. CodeMirror's default paints them #404740 -- all but the ink
|
|
708
|
+
* colour -- because every marker carries tags.processingInstruction, a child
|
|
709
|
+
* of meta, and defaultHighlightStyle is loaded here for its code colours.
|
|
710
|
+
*
|
|
711
|
+
* A flat grey rather than a faded ink: fading a marker into the paper is what
|
|
712
|
+
* made an H1 and an H2 indistinguishable while one was being typed.
|
|
713
|
+
*/
|
|
714
|
+
const MARKER_INK = '#757575';
|
|
715
|
+
|
|
672
716
|
const markdownStyle = HighlightStyle.define([
|
|
673
717
|
// textDecoration: 'none' is deliberate -- defaultHighlightStyle, loaded for
|
|
674
718
|
// the colours inside code fences, underlines every heading.
|
|
719
|
+
// One entry covers every marker: the hashes, the stars, the backticks, the
|
|
720
|
+
// quote arrow, the list dash, a table's pipes and a strikethrough's tildes
|
|
721
|
+
// all carry this tag.
|
|
722
|
+
{ tag: tags.processingInstruction, color: MARKER_INK },
|
|
675
723
|
{ tag: tags.heading, textDecoration: 'none' },
|
|
676
724
|
{ tag: tags.heading1, fontSize: '1.35em', fontWeight: '600', textDecoration: 'none' },
|
|
677
725
|
{ tag: tags.heading2, fontSize: '1.18em', fontWeight: '600', textDecoration: 'none' },
|
|
@@ -777,6 +825,17 @@ function verticalStep(forward, base, extend) {
|
|
|
777
825
|
};
|
|
778
826
|
}
|
|
779
827
|
|
|
828
|
+
/**
|
|
829
|
+
* Enter carries a list on: a new bullet, the next number, another empty task
|
|
830
|
+
* box, or a second quote line -- and a second Enter on an item with nothing in
|
|
831
|
+
* it ends the list instead of adding to it.
|
|
832
|
+
*
|
|
833
|
+
* Without this, pressing Enter after "- [ ] milk" left a bare line, and typing
|
|
834
|
+
* "[ ] eggs" there looks like a task and is not one: the box only means
|
|
835
|
+
* anything inside a list item.
|
|
836
|
+
*/
|
|
837
|
+
const continueList = [{ key: 'Enter', run: insertNewlineContinueMarkup }];
|
|
838
|
+
|
|
780
839
|
const verticalKeymap = [
|
|
781
840
|
{ key: 'ArrowUp', run: verticalStep(false, cursorLineUp), preventDefault: true },
|
|
782
841
|
{ key: 'ArrowDown', run: verticalStep(true, cursorLineDown), preventDefault: true },
|
|
@@ -804,6 +863,7 @@ function createLiveEditor({ parent, doc, onChange }) {
|
|
|
804
863
|
// Ahead of the defaults, so Mod-i and friends are not swallowed.
|
|
805
864
|
Prec.high(keymap.of(shortcuts)),
|
|
806
865
|
Prec.high(keymap.of(verticalKeymap)),
|
|
866
|
+
Prec.high(keymap.of(continueList)),
|
|
807
867
|
// Search ahead of the defaults: Cmd+F must open the panel rather than
|
|
808
868
|
// fall through to anything else bound to it.
|
|
809
869
|
Prec.high(keymap.of(searchKeymap)),
|
package/src/renderer/note.css
CHANGED
|
@@ -154,11 +154,36 @@ body.editing #tools > * { visibility: visible; }
|
|
|
154
154
|
.cm-md-h3 { font-size: 1.05em; font-weight: 600; }
|
|
155
155
|
.cm-md-h4, .cm-md-h5, .cm-md-h6 { font-weight: 600; }
|
|
156
156
|
|
|
157
|
-
.cm-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
/* Scoped to .cm-editor because `.cm-editor .cm-line { padding: 0 }` above
|
|
158
|
+
zeroes it, and a bare .cm-md-quote loses to that -- which is why quoted
|
|
159
|
+
text sat hard against its own bar with no inset at all. The padding also
|
|
160
|
+
gives a wrapped quote its hanging indent, for nothing. */
|
|
161
|
+
.cm-editor .cm-md-quote {
|
|
162
|
+
--quote-depth: 1;
|
|
163
|
+
position: relative;
|
|
164
|
+
padding-left: calc(var(--quote-depth) * 0.9em + 0.4em);
|
|
160
165
|
opacity: 0.85;
|
|
161
166
|
}
|
|
167
|
+
.cm-editor .cm-md-quote-2 { --quote-depth: 2; }
|
|
168
|
+
.cm-editor .cm-md-quote-3 { --quote-depth: 3; }
|
|
169
|
+
|
|
170
|
+
/* One bar per nesting level, drawn as a repeating gradient rather than a
|
|
171
|
+
border: a line has only one border-left however deep the quote is. The
|
|
172
|
+
gradient repeats on the same interval as the indent, so it draws exactly
|
|
173
|
+
as many bars as there are levels. */
|
|
174
|
+
.cm-editor .cm-md-quote::before {
|
|
175
|
+
content: '';
|
|
176
|
+
position: absolute;
|
|
177
|
+
left: 0;
|
|
178
|
+
top: 0;
|
|
179
|
+
bottom: 0;
|
|
180
|
+
width: calc(var(--quote-depth) * 0.9em);
|
|
181
|
+
background: repeating-linear-gradient(
|
|
182
|
+
to right,
|
|
183
|
+
rgba(0, 0, 0, 0.22) 0 2px,
|
|
184
|
+
transparent 2px 0.9em
|
|
185
|
+
);
|
|
186
|
+
}
|
|
162
187
|
|
|
163
188
|
/* Smaller text, but the same line box as prose. CodeMirror moves the caret
|
|
164
189
|
vertically by one default line height, so a line shorter than that gets
|
|
@@ -237,9 +262,13 @@ body.editing #tools > * { visibility: visible; }
|
|
|
237
262
|
opacity: 0;
|
|
238
263
|
transition: max-width 0.12s ease, opacity 0.12s ease;
|
|
239
264
|
}
|
|
265
|
+
/* Full strength, not a hint. A revealed marker is there to be edited -- you
|
|
266
|
+
are counting hashes to see what level a heading is, or putting the caret
|
|
267
|
+
between a pair of stars -- and a washed-out one is hard to read against
|
|
268
|
+
the paper. It is the hiding that keeps the note clean, not the fading. */
|
|
240
269
|
.cm-editor .cm-md-marker-open {
|
|
241
270
|
max-width: 4em;
|
|
242
|
-
opacity:
|
|
271
|
+
opacity: 1;
|
|
243
272
|
}
|
|
244
273
|
|
|
245
274
|
/* Sits at the end of a block's opening fence line, beside the language name. */
|