sdocs-dev 1.1.0 → 1.1.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/LICENSE +21 -0
- package/bin/sdocs-dev.js +81 -14
- package/package.json +1 -1
- package/public/css/layout.css +4 -1
- package/public/css/mobile.css +1 -1
- package/public/css/rendered.css +12 -1
- package/public/css/tokens.css +5 -2
- package/public/default.md +125 -96
- package/public/fonts/inter-400.woff2 +0 -0
- package/public/fonts/inter-500.woff2 +0 -0
- package/public/fonts/inter-600.woff2 +0 -0
- package/public/images/examples.png +0 -0
- package/public/index.html +20 -7
- package/public/sdocs-app.js +62 -12
- package/public/sdocs-controls.js +26 -39
- package/public/sdocs-export.js +2 -1
- package/public/sdocs-styles.js +85 -1
- package/public/sdocs-theme.js +7 -1
- package/public/sw.js +106 -0
- package/public/vendor/marked.min.js +6 -0
- package/server.js +65 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Josh Summers
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/bin/sdocs-dev.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* sdoc CLI
|
|
4
4
|
* Usage:
|
|
5
5
|
* sdoc report.md # open file in browser
|
|
6
|
-
* sdoc share report.md # shareable
|
|
6
|
+
* sdoc share report.md # copy shareable link to clipboard
|
|
7
7
|
* sdoc new # blank document in write mode
|
|
8
8
|
* cat file.md | sdoc # pipe markdown to browser
|
|
9
9
|
* sdoc # open studio with empty editor
|
|
@@ -14,8 +14,51 @@ const path = require('path');
|
|
|
14
14
|
const zlib = require('zlib');
|
|
15
15
|
const { execSync } = require('child_process');
|
|
16
16
|
const SDocYaml = require('../public/sdocs-yaml.js');
|
|
17
|
+
const SDocStyles = require('../public/sdocs-styles.js');
|
|
18
|
+
|
|
19
|
+
const https = require('https');
|
|
20
|
+
const os = require('os');
|
|
17
21
|
|
|
18
22
|
const DEFAULT_URL = 'https://sdocs.dev';
|
|
23
|
+
const VERSION = require('../package.json').version;
|
|
24
|
+
|
|
25
|
+
// ── Update check (cached, every 3 days) ──────────────────
|
|
26
|
+
|
|
27
|
+
const UPDATE_CACHE = path.join(os.homedir(), '.config', 'sdocs-dev', 'update-check.json');
|
|
28
|
+
const THREE_DAYS = 3 * 86400000;
|
|
29
|
+
|
|
30
|
+
function checkForUpdate() {
|
|
31
|
+
if (!process.stdout.isTTY || process.env.NO_UPDATE_NOTIFIER || process.env.CI) return;
|
|
32
|
+
|
|
33
|
+
// Skip if checked recently
|
|
34
|
+
try {
|
|
35
|
+
if (Date.now() - fs.statSync(UPDATE_CACHE).mtimeMs < THREE_DAYS) return;
|
|
36
|
+
} catch (_) {}
|
|
37
|
+
|
|
38
|
+
console.log('Checking for updates...');
|
|
39
|
+
https.get('https://registry.npmjs.org/-/package/sdocs-dev/dist-tags', { timeout: 3000 }, res => {
|
|
40
|
+
let data = '';
|
|
41
|
+
res.on('data', chunk => { data += chunk; });
|
|
42
|
+
res.on('end', () => {
|
|
43
|
+
try {
|
|
44
|
+
const latest = JSON.parse(data).latest;
|
|
45
|
+
// Update cache timestamp
|
|
46
|
+
fs.mkdirSync(path.dirname(UPDATE_CACHE), { recursive: true });
|
|
47
|
+
fs.writeFileSync(UPDATE_CACHE, JSON.stringify({ latest }));
|
|
48
|
+
|
|
49
|
+
const a = latest.split('.').map(Number);
|
|
50
|
+
const b = VERSION.split('.').map(Number);
|
|
51
|
+
let newer = false;
|
|
52
|
+
for (let i = 0; i < 3; i++) { if (a[i] > b[i]) { newer = true; break; } if (a[i] < b[i]) break; }
|
|
53
|
+
if (newer) {
|
|
54
|
+
console.log(`Update available: ${VERSION} \u2192 ${latest} \u2014 run \`npm i -g sdocs-dev\` to update`);
|
|
55
|
+
} else {
|
|
56
|
+
console.log(`Up to date (v${VERSION})`);
|
|
57
|
+
}
|
|
58
|
+
} catch (_) {}
|
|
59
|
+
});
|
|
60
|
+
}).on('error', () => {}).on('timeout', function () { this.destroy(); });
|
|
61
|
+
}
|
|
19
62
|
|
|
20
63
|
// ── Help ───────────────────────────────────────────────────
|
|
21
64
|
const HELP = `
|
|
@@ -29,14 +72,14 @@ USAGE
|
|
|
29
72
|
sdoc <file> --style Open with style panel
|
|
30
73
|
sdoc <file> --raw Open raw markdown source
|
|
31
74
|
sdoc new New blank document (write mode)
|
|
32
|
-
sdoc share <file>
|
|
33
|
-
sdoc share <file> --section "X"
|
|
75
|
+
sdoc share <file> Copy shareable link to clipboard
|
|
76
|
+
sdoc share <file> --section "X" Link with section anchor
|
|
34
77
|
sdoc schema Print the full styles schema
|
|
35
78
|
sdoc defaults Show ~/.sdocs/styles.yaml
|
|
36
79
|
sdoc defaults --reset Remove default styles
|
|
37
80
|
sdoc help Show this help
|
|
38
81
|
cat file.md | sdoc Pipe markdown from stdin
|
|
39
|
-
cat file.md | sdoc share Pipe to
|
|
82
|
+
cat file.md | sdoc share Pipe to clipboard link
|
|
40
83
|
|
|
41
84
|
MODE FLAGS
|
|
42
85
|
--read Clean reading view (default when file given)
|
|
@@ -46,6 +89,8 @@ MODE FLAGS
|
|
|
46
89
|
|
|
47
90
|
OPTIONS
|
|
48
91
|
--section <heading> Scroll to heading section on load
|
|
92
|
+
--light Open in light theme
|
|
93
|
+
--dark Open in dark theme
|
|
49
94
|
--url <base> Custom base URL (default: https://sdocs.dev)
|
|
50
95
|
--mode <m> Alias for --read / --write / --style / --raw
|
|
51
96
|
|
|
@@ -84,7 +129,7 @@ GENERAL
|
|
|
84
129
|
baseFontSize number Base font size in px. All rem/em values scale from this.
|
|
85
130
|
Default: 16
|
|
86
131
|
background string Page background color (hex).
|
|
87
|
-
Default: "#ffffff" (light) / "#
|
|
132
|
+
Default: "#ffffff" (light) / "#2c2a26" (dark)
|
|
88
133
|
color string Master body text color (hex). Cascades to headings,
|
|
89
134
|
paragraphs, and lists unless those are overridden.
|
|
90
135
|
Default: "#1c1917"
|
|
@@ -135,6 +180,7 @@ BLOCKQUOTE
|
|
|
135
180
|
blockquote:
|
|
136
181
|
borderColor string Left border accent color. Default: "#2563eb"
|
|
137
182
|
borderWidth number Left border thickness (px). Default: 3
|
|
183
|
+
background string Quote background color. Default: "#f7f5f2"
|
|
138
184
|
color string Quote text color. Default: "#6b6560"
|
|
139
185
|
|
|
140
186
|
COLOR CASCADE
|
|
@@ -158,7 +204,7 @@ THEME COLORS
|
|
|
158
204
|
h1: { color: "#c0392b" }
|
|
159
205
|
link: { color: "#2563eb" }
|
|
160
206
|
dark:
|
|
161
|
-
background: "#
|
|
207
|
+
background: "#2c2a26"
|
|
162
208
|
color: "#e7e5e2"
|
|
163
209
|
h1: { color: "#ef6f5e" }
|
|
164
210
|
link: { color: "#60a5fa" }
|
|
@@ -190,7 +236,7 @@ EXAMPLE — editorial article with colored heading tiers
|
|
|
190
236
|
h2: { color: "#8e44ad" }
|
|
191
237
|
h3: { color: "#16a085" }
|
|
192
238
|
link: { color: "#e67e22", decoration: "underline" }
|
|
193
|
-
blockquote: { borderColor: "#c0392b",
|
|
239
|
+
blockquote: { borderColor: "#c0392b", background: "#faf0eb", color: "#7f8c8d" }
|
|
194
240
|
dark:
|
|
195
241
|
background: "#1a1520"
|
|
196
242
|
color: "#e7e5e2"
|
|
@@ -199,7 +245,7 @@ EXAMPLE — editorial article with colored heading tiers
|
|
|
199
245
|
h2: { color: "#c490e4" }
|
|
200
246
|
h3: { color: "#5ed4b8" }
|
|
201
247
|
link: { color: "#f0a860", decoration: "underline" }
|
|
202
|
-
blockquote: { borderColor: "#ef6f5e",
|
|
248
|
+
blockquote: { borderColor: "#ef6f5e", background: "#221a28", color: "#9e9590" }
|
|
203
249
|
---
|
|
204
250
|
`;
|
|
205
251
|
|
|
@@ -237,6 +283,7 @@ function parseArgs(argv) {
|
|
|
237
283
|
let url = null;
|
|
238
284
|
let subcommand = null;
|
|
239
285
|
let section = null;
|
|
286
|
+
let theme = null;
|
|
240
287
|
let resetFlag = false;
|
|
241
288
|
|
|
242
289
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -251,6 +298,8 @@ function parseArgs(argv) {
|
|
|
251
298
|
if (arg === '--style') { mode = 'style'; continue; }
|
|
252
299
|
if (arg === '--raw') { mode = 'raw'; continue; }
|
|
253
300
|
if (arg === '--read') { mode = 'read'; continue; }
|
|
301
|
+
if (arg === '--light') { theme = 'light'; continue; }
|
|
302
|
+
if (arg === '--dark') { theme = 'dark'; continue; }
|
|
254
303
|
|
|
255
304
|
// Long-form --mode
|
|
256
305
|
if (arg === '--mode' || arg === '-m') {
|
|
@@ -280,7 +329,7 @@ function parseArgs(argv) {
|
|
|
280
329
|
if (!file) { file = arg; continue; }
|
|
281
330
|
}
|
|
282
331
|
|
|
283
|
-
return { file, mode, url, subcommand, section, resetFlag };
|
|
332
|
+
return { file, mode, url, subcommand, section, theme, resetFlag };
|
|
284
333
|
}
|
|
285
334
|
|
|
286
335
|
// ── Build URL ─────────────────────────────────────────────
|
|
@@ -290,6 +339,17 @@ function buildUrl(content, opts) {
|
|
|
290
339
|
const params = new URLSearchParams();
|
|
291
340
|
|
|
292
341
|
if (content) {
|
|
342
|
+
// Strip default style values to produce shorter URLs
|
|
343
|
+
const parsed = SDocYaml.parseFrontMatter(content);
|
|
344
|
+
if (parsed.meta && parsed.meta.styles) {
|
|
345
|
+
const stripped = SDocStyles.stripStyleDefaults(parsed.meta.styles);
|
|
346
|
+
if (Object.keys(stripped).length > 0) {
|
|
347
|
+
parsed.meta.styles = stripped;
|
|
348
|
+
} else {
|
|
349
|
+
delete parsed.meta.styles;
|
|
350
|
+
}
|
|
351
|
+
content = SDocYaml.serializeFrontMatter(parsed.meta) + '\n' + parsed.body;
|
|
352
|
+
}
|
|
293
353
|
params.set('md', compressToBase64Url(content));
|
|
294
354
|
} else if (opts.defaultStyles) {
|
|
295
355
|
const stylesJson = JSON.stringify(opts.defaultStyles);
|
|
@@ -299,6 +359,8 @@ function buildUrl(content, opts) {
|
|
|
299
359
|
const mode = opts.mode || (content ? 'read' : 'style');
|
|
300
360
|
if (mode && mode !== 'read') params.set('mode', mode);
|
|
301
361
|
|
|
362
|
+
if (opts.theme) params.set('theme', opts.theme);
|
|
363
|
+
|
|
302
364
|
if (opts.section) {
|
|
303
365
|
params.set('sec', slugify(opts.section));
|
|
304
366
|
}
|
|
@@ -457,26 +519,31 @@ if (require.main === module) {
|
|
|
457
519
|
const url = buildUrl(content, {
|
|
458
520
|
url: opts.url,
|
|
459
521
|
mode: opts.mode,
|
|
522
|
+
theme: opts.theme,
|
|
460
523
|
defaultStyles: !content ? defaults : null,
|
|
461
524
|
section: opts.section,
|
|
462
525
|
});
|
|
463
526
|
|
|
464
|
-
// Share:
|
|
527
|
+
// Share: copy to clipboard
|
|
465
528
|
if (opts.subcommand === 'share') {
|
|
466
|
-
process.stdout.write(url + '\n');
|
|
467
529
|
try {
|
|
468
530
|
const clip = process.platform === 'darwin' ? 'pbcopy'
|
|
469
531
|
: execSync('which xclip 2>/dev/null', { encoding: 'utf-8' }).trim() ? 'xclip -selection clipboard'
|
|
470
532
|
: 'xsel --clipboard --input';
|
|
471
533
|
execSync(clip, { input: url, stdio: ['pipe', 'ignore', 'ignore'] });
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
534
|
+
const name = opts.file ? path.basename(opts.file) : 'stdin';
|
|
535
|
+
console.log(`\u2713 Link for ${name} copied to clipboard`);
|
|
536
|
+
} catch (_) {
|
|
537
|
+
process.stdout.write(url + '\n');
|
|
538
|
+
}
|
|
539
|
+
checkForUpdate();
|
|
540
|
+
return;
|
|
475
541
|
}
|
|
476
542
|
|
|
477
543
|
// Default: open browser
|
|
478
544
|
openBrowser(url);
|
|
479
545
|
console.log(`SDocs → ${url.length > 80 ? url.slice(0, 77) + '...' : url}`);
|
|
546
|
+
checkForUpdate();
|
|
480
547
|
})().catch(e => {
|
|
481
548
|
console.error('sdoc:', e.message);
|
|
482
549
|
process.exit(1);
|
package/package.json
CHANGED
package/public/css/layout.css
CHANGED
package/public/css/mobile.css
CHANGED
package/public/css/rendered.css
CHANGED
|
@@ -135,9 +135,20 @@
|
|
|
135
135
|
#rendered .md-section-body:not(.open):not(:has(.md-section-body.open)) > :not(.md-section) {
|
|
136
136
|
display: none;
|
|
137
137
|
}
|
|
138
|
+
#rendered .md-section-body:not(.open):not(:has(.md-section-body.open)):has(> :not(.md-section)):has(> .md-section)::before {
|
|
139
|
+
content: "\2026";
|
|
140
|
+
display: block;
|
|
141
|
+
opacity: 1;
|
|
142
|
+
font-weight: bold;
|
|
143
|
+
padding-left: calc(1.2em + 20px);
|
|
144
|
+
font-size: 1rem;
|
|
145
|
+
color: var(--text-3);
|
|
146
|
+
padding-bottom: 10px;
|
|
147
|
+
line-height: 0.1;
|
|
148
|
+
}
|
|
138
149
|
#rendered .md-section-body:not(.open):has(.md-section-body.open) > :not(.md-section) {
|
|
139
150
|
opacity: 0.55;
|
|
140
|
-
padding-left: 1.2em;
|
|
151
|
+
padding-left: calc(1.2em + 20px);
|
|
141
152
|
}
|
|
142
153
|
#rendered .md-section-body:not(.open) > .md-section:not(:has(> .md-section-body.open)) {
|
|
143
154
|
padding-left: 1.2em;
|
package/public/css/tokens.css
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url('/public/fonts/inter-400.woff2') format('woff2'); }
|
|
2
|
+
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url('/public/fonts/inter-500.woff2') format('woff2'); }
|
|
3
|
+
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url('/public/fonts/inter-600.woff2') format('woff2'); }
|
|
4
|
+
|
|
1
5
|
:root {
|
|
2
6
|
--bg: #F7F5F2;
|
|
3
7
|
--bg-surface: #F1EDE8;
|
|
@@ -84,7 +88,7 @@ html[data-theme="dark"] {
|
|
|
84
88
|
--md-copy-btn-hover: rgba(255,255,255,0.06);
|
|
85
89
|
}
|
|
86
90
|
html[data-theme="dark"] :is(#rendered, #write) {
|
|
87
|
-
--md-bg: #
|
|
91
|
+
--md-bg: #2c2a26;
|
|
88
92
|
--md-color: #e7e5e2;
|
|
89
93
|
--md-code-bg: #1a1816;
|
|
90
94
|
--md-code-color: #b8a99a;
|
|
@@ -102,7 +106,6 @@ body,
|
|
|
102
106
|
#right,
|
|
103
107
|
#export-panel,
|
|
104
108
|
#statusbar,
|
|
105
|
-
#content-area,
|
|
106
109
|
#left-toolbar,
|
|
107
110
|
#right-header,
|
|
108
111
|
#export-panel-header,
|