sdocs-dev 1.1.1 → 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 +71 -6
- 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 +123 -94
- 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
|
@@ -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 = `
|
|
@@ -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,6 +519,7 @@ 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
|
});
|
|
@@ -473,12 +536,14 @@ if (require.main === module) {
|
|
|
473
536
|
} catch (_) {
|
|
474
537
|
process.stdout.write(url + '\n');
|
|
475
538
|
}
|
|
476
|
-
|
|
539
|
+
checkForUpdate();
|
|
540
|
+
return;
|
|
477
541
|
}
|
|
478
542
|
|
|
479
543
|
// Default: open browser
|
|
480
544
|
openBrowser(url);
|
|
481
545
|
console.log(`SDocs → ${url.length > 80 ? url.slice(0, 77) + '...' : url}`);
|
|
546
|
+
checkForUpdate();
|
|
482
547
|
})().catch(e => {
|
|
483
548
|
console.error('sdoc:', e.message);
|
|
484
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,
|
package/public/default.md
CHANGED
|
@@ -1,36 +1,85 @@
|
|
|
1
1
|
---
|
|
2
2
|
styles:
|
|
3
|
-
fontFamily:
|
|
3
|
+
fontFamily: Inter
|
|
4
4
|
baseFontSize: 16
|
|
5
5
|
lineHeight: 1.75
|
|
6
|
-
headers: { fontFamily: "inherit", scale: 1, marginBottom: 0.4 }
|
|
7
6
|
h1: { fontSize: 2.1, fontWeight: 700 }
|
|
8
7
|
h2: { fontSize: 1.55, fontWeight: 600 }
|
|
9
8
|
h3: { fontSize: 1.2, fontWeight: 600 }
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
p: { lineHeight: 1.75, marginBottom: 1 }
|
|
10
|
+
light:
|
|
11
|
+
background: "#ffffff"
|
|
12
|
+
color: "#1c1917"
|
|
13
|
+
dark:
|
|
14
|
+
background: "#2c2a26"
|
|
15
|
+
color: "#e7e5e2"
|
|
16
16
|
---
|
|
17
|
+
|
|
17
18
|
# Say hello to SmallDocs: A markdown-first replacement for Word & GDocs
|
|
18
19
|
|
|
19
20
|
If you're working with agents, a document written in markdown is <ins>officially</ins>* 407 times more useful than a document locked inside a `.docx` or `.gdoc` file format. Because of this, I believe Word and GDocs' days are numbered. (*I am the official.)
|
|
20
21
|
|
|
21
22
|
But while markdown is great for agents, it's a bit annoying for humans. Quickly and elegantly reading a `.md` file requires you to open your code editor and enter "preview" mode. Sharing a markdown file requires you to actually send the file to someone. They then have to download it and find the least annoying way to read it.
|
|
22
23
|
|
|
23
|
-
SmallDocs is an [open source](https://github.com/JoshInLisbon/SDocs) attempt at something different. It lets you (or your agent) easily, elegantly and privately **read**, **
|
|
24
|
+
SmallDocs is an [open source](https://github.com/JoshInLisbon/SDocs) attempt at something different. It lets you (or your agent) easily, elegantly and <ins>100% privately</ins> **read**, **format**, **share** and **export** `.md` files.
|
|
25
|
+
|
|
26
|
+
Reading a `.md` file in SmallDocs feels just like this (you're reading markdown right now). And by playing with the styles, it can feel like [this](https://sdocs.dev/#md=fVbNbttGEL7zKQb2IQAhEaJky4oOKdIEboq2QFoHNXLTcHcobkXuMrtDyWpRoA_RJ-yTFLMkHcm1e5N2vpn55psfaTqdJoGPNYV1AlA6y7fYmPq4hh-dxwSgwEC3zvKd-Z3WkK8SgNpY-kBmW_Ea8mx1nQBUhJp8jHEe5WONxxKNh_cmtDUeI6BBvzX2W8fsmjXMshghX8Mf0bXPNM9Wk_j1fsh0M5vBnwKcnwPzF4GLp8DFOXA5AFvBPS1q8oRlni0iuDZ2J3hNynlk4-waOqvJS4CIKGqndl86x_Q0fz6BwnlN_t5ortYwRpSkUZgC1W7rXWf1Gi4uSyyX5ewiWpSrnZfHxWK-zLF_7DV7NK2KHHO8iEFHnR6NOLuez_WjcXFmXBZX8_lyNLZntoWeU16OtrH8F5Iqp2PV54VQSSutLyZfvW7UFeY0ep0r1kv0boSqq9fXS7yYPBVnQZquTmMui-vV1aqPqdHvnpM0x3yZP5WUZvpaXT8raTnD2c1LkuornC3xBUkH2s9KqpSaFbMXJT1P-rykc5wv8tVp-eds_lfSG1xeX8_-I-l8kVO-PI2Jq9erVSQ6nU6T5BI-VQQ_d4YYfqG9qzuZf3ClrLmx8IkeOElunZftQE1hAlwRaKe6hizDAQMg7E3osAb0bEpUDKXxgQGtBgTlLKOx5CWo0YQBAilndQb3BKpygeJGBSiodJ7gML4enNcholD_1gUmPSzwKfTRhH4bKYUslsTO1RAqbElHxlwZuzN2myXJT-h32h0sGLsnz0FsIYPvGTDswhrSQ4UMpox-kQQobKiv6ps0SS4v4d4bNnYL94Yr1zHceqNEuSSR5AUFhsMAESYBtAnYtoQ-g7dgHVPh3A60o2BfxcRwdB2wk9qleASmpq2RSRwOlREP9PrRxdOXzngB1kaRDZQNLWN6YKiwLwuCMP_SYW34CP_89TcYhi1xAGHthhrxmCXJG7gQ7kGhN8K_cbHDJgDWBzwGEKFH5YVsYPScwduSyQNXyJOo8lbksuBsfZRMUBAz-ewiZr9jaiuy8IOx2yS5l49H13konW8wJmsfi5gA7ckf-1YWpFxDMmynumbwWbyZfGMsjl_byll6FaLMAbBtRcLN3jQbCBRCHPA9ebi7-wDIMMemn5m-1yii9hWjUoIvapqMT63zjKcPQj9kMhSXcC-D89l18B0amyRTSNOPEW9E_jSNEnwtUBLuQl_loSJP8mPm5Wt0_ZV85Cob5F09uG-2hkGbstxAqNwhEgB6QMX1EeLkqgrtlnSM8c7VNRbDz9oQoSG_JYla1kZxX7An1H1Z1jEoZAzsXVsZFcPcOtWFwd26KH2BHhpCKzLLdLPHYQPOlPiBqE0S-aSdjG3d77t0W7oYOwZbUSQ1lslKCKzTM8jb-GdEei7DCJvLzQSoaSsMJsjTJsXA5E3YhXQzGe5ObQIP-Omm729g3ynuPEFt9hRA2iCnQVphOFBdZkmy2WziVexD45g6-frSosetx7aCg-EK0rRwtU7TmDc1jLVRaQyaJdOTQD0hpiZ5c_IaD3pMKmdFWL4fbysGeI-MSfJW7l5_skpTkziO_YLiCFXXSBskfYs-0PjeoKqMpXgQY7p4aaAx1nlp_56sIatoOAuvhE7ZWY2SHeV6mpKHlRoPfhgWETQyxqUH3Mr7BILyppUPQqQ_enIHZMZ7obTxJFOa9Udyk2mnHja9DEWNageFe-g7tcka3VssuJYsyLXMkn8B&theme=light), [this](https://sdocs.dev/#md=jVbdbuPKDb7XUxDKRQDDEWzH3nh90WB_uqc5OH9oUgS9W1pDWVOPZnSGVBy1KNCH6BP2SQqOrDjJZoFzY1iajxzyI_lRFxcXGUvviDcZQBW8fMHGun4D-S-dtxLyDGCLTF-Cl1v7T9rA_CoDcNbTX8juatnAvFhnADWhocgb-NdLN7-FtrWe8ylwiU7tp9Bg3Fn_MYiEZgOzYgX_Vg_z0Xi4aFGspunx_njR1Ww2ABcvgfPi3feQl6-Ri5fAdyNw-Qr4NqxV1KvcX6czL-ZDPs76veINlSGi2OA3kHfeUFQPecKUwdB48wby4eXWhXL_exckHW1DNBTvrZF6A8vpi2xm400sCuUWS-t3SulyCtYb8inGxRGlIWcAWtFyv4uh82YD-VmF1aqq8nRSBheivlyY-XZJw8uhMk9HV-UlkhliHavxdLil-Xp1Orx8cTgz75fr9Xg4EvQdxyM3r4Kd0VWF-fRk9f7y8pJwtHqLvE8jtJytl1WZT1_7vKQyvX7yudoul1fHQA3G_Vu0Ld8tF6vFK9qMMbNq9iZt5XK7qr5HW_Ue12b5HdpWRHg6_Ia2l47fpm0xm68uV89TNOvtsqI_RNt6uyqrd9_QNi_ni8vZc5-4fr8uZ-rz4uIiy87gM7HdefgtWl_a1hFDFSLcheAY7moU-AlZsuyuJtgSC0g6wUj-XEBqguCJ4WClTk9NYIGKULpIXMBdTf15pBNQ1GVF5GBiPT1Ywa2jCfzvP_8FZ_cJ2EMZOmfUf40Pei15MClMMoC-hyA1RThgX2TZ2RncCkaB-zGEL0TO-l2WfaQqRII-dHCIVggQ2PqdoyQQEKpUiSmg5wNFkNryBupwAK41AODQUPAEE413Aoea_BBfx8lrTGRcZ9mfID_SaBl8EPhHxwIHTdUKuBD2PGSH3qTkh8cCTlZ6rRU4hLjnIk983Ao9EPwYtpxlvwSB_DBwN3A7BmkHlgaTxG4ZGmJwKBRTAdKN1u_0GuWn0u5IalfAnx8o9lLrKTkmhdimddSQlwGiDJ-NnDLchyj1MW61-hJill3AZPIp-Moa8iVNhnLmN7D34QD0iKW4fqBDOYaDdQ5MyJPdbUtkRpPUcAdkqJAllQQ93AA9tlQKmcHiMyWdHG1-racp73MGBG9LAgldWQ_Yu9jxE_LnHgwKapKMFUFNkfLUQX-lJjwQ_M2LdXAj8DES7jnLEj0j5WqHUAaWAm6G61If2dQVYELJU0BoMfVhDN1u6Mdjk8HvHbFSOhp0TPGcobHeDHU6AVibPz9W-ECAxiTqrocyW709NyHNk2UgjB6ssJa-dfRopb_Oh8Kp37s6EsEPKMRPQ4FPWXFtW55qHzXatS0yAzqnKRBtsmxewGRywwogZvJi0V1PJnCfgkNoCLUTqs6B75otRR0szU1T42RnK_01Ns00PVqW62xxcmu9dFbsA6nbT-ifBk_nTIfCSh06gUhotOeU6evs8uRA0w6sSqIePisvx1kahGnQi3FyphAimCOojIRCwC2VFh2UyMTXWXZTPWOoQquS53vYoVCi6oBWuIAfQlCZqOSAQ3-0KJa8DLr0gVhqElsyfIgEOsO_tlpgdFn2KZhkoUlBE5JGoh9Kq2ol5Au48UKxwpKS4gKrEp6wchTX0tlyT8cmerDcoQOHPcVjI50-b07dU4am6bwtxyH_-vVr9gEO5NxFFWKDImSU6E6VABj79PmZ3x4rU2JULd5qWbQFizxTc-0brb5PX6wGhB6fGd9pr6YhMhErKeBn7LdU5Ony7K5vwy5iW_fT8TNpmgQz7a5x47jusYv2-WpR5VSJF0pNS4lRihuYpNlIqUQeXHEYCj-It21octwgtW2n6tHDT5aF_LDw0jIz6HcUQ8fQ1shpbWw768wzRVVGDqomQyC19Xv9p9GZ4KmAvw8PPkiR7kpW3KBzulIfKLIWJ6k3Ndtg7Bhj-7SWVeTIwwGlrFPOLYXWEWApHTrXH4elyLL7QWipB7vzqVnIOR7WYDoKEJPeFXCCssRup6tx2OTfWLCuBVv1z22Q9-lj4Vt0Igg8PWo8yqQuymM3VtZbrsk87dNI52lrpi3kqEoe0Jijyv4xvKCu1_RJ8H8&theme=dark) or even [this](https://sdocs.dev/#md=pVbdbtvMEb3nUwysiwCqKFiSJdkC2iBxkiZA2y-wnaa903B3KG603GF2llKUtkAfok_YJyl2KSqy41xVd-L87NkzZw6Z53km4WBJVhlAyS68w9rYwwruufWK4B6dwCwDKFDoHbtwb77TCibzDMAaR-_JbKqwgsl4mQFUhJp86vW42x0XHBjuLRYpVqPfGPeaQ-B6BZfjq1g8WcE_UlV3xnQ8HaW_n49nLC8v4V8xcfo4cTKe_yJx9jRx8iRzccxsYuKT-4x-QnmTchVr6vuuoGbH0qCiFLPGbWNMk2KPwbBbgWPXBQvLavu15ZDKC_aa_GejQ7WCq2O1hBiK_YzbxCNn82MkokrUFai2G8-t0yu4GJTzclniRYootuzjwwlOZ7Np97Bj9RS6VFe4oIvUtGfyR918Tsv5KTh7HKRFOVd9sL_oj8azxQ1O-nDP0WOwdE1Uzi9GP6oWenqtb_qq5wi6PR1AOKebi9HTnrq8KvG85xXO54vrrqdGv32Otks1mU4vn9CmrvWC5s_SttR6Vqpf0LZY0nV58wvaFkTLYvlL2h43fp62yWJ6OZueXxGX10WJ_w9tk8kEp49ou76-ucHUM8_zLBvA69ZYbdwGEG7_9AEeKgxwR9KQCgIfgsAnIS9Z9go2zBoU1zU6ncctgsBsQSpurYaSyII1WwIExW5HXtJmwN6EChC2jveW9IawsBTxWMJNS_Dff_8n5isjNIKKbFO2dgToNDjakY8xTaLIRZBjeE-eXghUvIfAUETwwI7GWTYYwEdvnDKNJZis4A3Db47goYqX-0zWZtnbHfkDSFscb9Fjr3AXYYtxG0sj4GJnuBVoWt-w0Bg-lHDgFhS6FwEiGm8Kgn2kCntGQDMJGBfRgJAL5BSNwIQXApojhsAMdauqcZat1-sCpcoG8EdmvQJlCX1_XiaaFUiFnuDu7as3f347rjWcfgNQ3ByO8ai0SISypikYvT4Wq4pqhJ9_A2i8cQHS2wAazw35YEiSElCvAOvCbFpupWvUeFYkcoYjz7kNTRt-n47O89PJkOeO88LzXsjHG2bZQ0XwyZlv0FTGsnBTHcBIJPFLKwEcS0C7MZhEkJhKGvv0t6fjnK7gnpyYKJ03VGJrg4zgXWst3LILnm132HfynCt2pdm0nTEDfWvImziMftoFAX1TZC25MIbXbYB9RQ7aqHNwRFHkqecINmZHYEJkOFRUJy1zG0CRp5rd4WyUiS5PDfvwaFw97-nGe_ZbSbf1hBpq1jQC3V2om4k8bZTn3axOjRLCCGhPuH0J3JCL4PqRoiP7cw-P-3Mwe3ShK0rv_5dQkU8XNdJN7g_wqtvuEEXu6WtrPAk8pjYuIEXeytaCEcCzkgoFSjSWNGAAEyQKr0Z_gC9cpPEOIE7sOE24D6i2WfZ3bn1PiAC5aJVx9zxwY5xhJ2O4rZiFuoFosqYgj4HsYZVlkzEMh78lfULJvsYwHCa6411rlpC2ldOwQaEQ7I2TbBrL_kq-YDHhcCz52hoKUBx6OCNY5_kuJdG6k0wcBelsFsvvsaRTrSYJvlUhygdVJEt6EjsOIzTDLruKpR_JJ7BO0bG-RIl6QB_aZgQWvx_AMiabLtlDNMZDSMZGVujJssxWvYGna380DUW3jhYeHT7NyJpd51bogBTLQQLVY_gQZ63oGEwseq6BQ0U-FUpy5gZFSKAzgiiDlCBjeM-OPYTKSLdDqMLZiiiMOx9Ioib_CUmkz3qUBG1ctyzPFnXe92zR787sXVKLLMonxw25cDIveAbAANAdouHpVpE_np6WIWr1rrUkifyeT7g1wXwnJ5VpsiyH4VCCjuZgujyNAYfDUcRF3vdPaxLBDUkqePvNhPQ1IFBjCOSP419frlOytCq67yh-XObR29LTuFatp9ThLwzGBYpER601nusmyHDY6bOjJG1mY5qu4j4Js_Wkj_Prk9d5_kXYrWN-GrBOuopL-oASCB5IQpbdta5byKSjMo2boDReAgRT06h74TsG9Ju2JhdkDJ87S2gacvKy-8aoOcZOy5mcWwIGOtrjjtDKudKxiOTGF398iaYXsj_KPOFJ_v0iYRhn_wM&theme=light):
|
|
27
|
+
|
|
28
|
+

|
|
24
29
|
|
|
25
|
-
|
|
30
|
+
Use SmallDoc's CLI for speed. Creating a SmallDoc for a `.md` file (+ automatically opening your browser to read it) is as simple as:
|
|
26
31
|
|
|
27
32
|
```
|
|
28
|
-
# npm i sdocs-dev
|
|
33
|
+
# npm i -g sdocs-dev
|
|
29
34
|
sdoc README.md
|
|
30
35
|
```
|
|
31
36
|
|
|
32
37
|
## How SmallDocs work
|
|
33
38
|
|
|
39
|
+
### URLs
|
|
40
|
+
|
|
41
|
+
The URL format for SmallDocs is:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
https://sdocs.dev/#md={compressed & encoded .md}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Your entire document (content and styles) lives in the URL hash.
|
|
48
|
+
|
|
49
|
+
To keep URLs as short as possible, SmallDocs compresses your markdown using [deflate](https://en.wikipedia.org/wiki/Deflate) (a standard compression algorithm built into every browser) and then encodes the result with [base64url](https://en.wikipedia.org/wiki/Base64#URL_applications) (a URL-safe variant of base64 that avoids characters like `+`, `/`, and `=` which would otherwise need percent-encoding).
|
|
50
|
+
|
|
51
|
+
The `mode` parameter controls which view opens. Valid values are `read` (clean reading view, style panel hidden), `style` (style panel visible), and `raw` (raw markdown editor). When sharing a link for someone to read, use `mode=read`:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
https://sdocs.dev/#md=...&mode=read
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
You can also link directly to a section using the `sec` parameter. Click any heading's link icon to copy its section URL:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
https://sdocs.dev/#md=...&sec=url-formatting
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The `sec` value is the heading text slugified (lowercased, spaces become hyphens, special characters stripped). The page will scroll to that section on load.
|
|
64
|
+
|
|
65
|
+
The `theme` parameter forces a specific theme: `theme=light` or `theme=dark`. This overrides the reader's system preference, which is useful when sharing a link where the document looks best in a particular theme.
|
|
66
|
+
|
|
67
|
+
### Privacy
|
|
68
|
+
|
|
69
|
+
Because the SmallDocs url format is:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
https://sdocs.dev/#md={compressed & encoded .md}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Your document never hits the SDocs server.
|
|
76
|
+
|
|
77
|
+
This layer of privacy is built into how HTTP works. The hash fragment (everything after the `#` in a URL) is never sent to the server by the browser. It always stays entirely client-side:
|
|
78
|
+
|
|
79
|
+
> "The fragment is not sent to the server when the URI is requested; it is processed by the client" - [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Fragment)
|
|
80
|
+
|
|
81
|
+
The [sdocs.dev](https://sdocs.dev) site is purely a rendering space. JavaScript reads `window.location.hash`, decompresses and decodes the content, and renders your `.md` locally. The server is about 60 lines of Node.js that serves static files — no database, no logging, no analytics.
|
|
82
|
+
|
|
34
83
|
### Formatting
|
|
35
84
|
|
|
36
85
|
SDocs adds basic styling to markdown files. You write your content in regular markdown and the styles live in a metadata block at the top of the file.
|
|
@@ -53,78 +102,70 @@ styles:
|
|
|
53
102
|
baseFontSize: 17
|
|
54
103
|
h1: { fontSize: 2.3, fontWeight: 700 }
|
|
55
104
|
p: { lineHeight: 1.9, marginBottom: 1.2 }
|
|
56
|
-
|
|
57
|
-
background: "#fffaf5"
|
|
58
|
-
color: "#1a1a2e"
|
|
59
|
-
h1: { color: "#c0392b" }
|
|
60
|
-
dark:
|
|
61
|
-
background: "#1a1520"
|
|
62
|
-
color: "#e7e5e2"
|
|
63
|
-
h1: { color: "#ef6f5e" }
|
|
105
|
+
...
|
|
64
106
|
---
|
|
65
107
|
```
|
|
66
108
|
|
|
67
|
-
Non-color properties (fonts, sizes, spacing) are shared across themes and live at the top level. Colors live inside `light:` and `dark:` blocks so both themes render correctly.
|
|
68
|
-
|
|
69
|
-
All color controls are in the **Colors** section of the style panel. The light/dark toggle at the top of that section lets you customize each theme independently. Colors cascade from general to specific — set `color` once and it flows to headings, paragraphs, and lists unless you override them individually.
|
|
70
|
-
|
|
71
109
|
(Click "**Raw**" — top left — to see the front matter for this file. See all available properties [here](https://sdocs.dev) or by running `npm i sdocs-dev; sdoc schema`.)
|
|
72
110
|
|
|
73
111
|
When a `Styled .md` file is rendered in the SmallDocs interface the specified styles are applied. If a plain `.md` file is rendered the default styles are applied.
|
|
74
112
|
|
|
75
|
-
|
|
113
|
+
#### Light & dark modes
|
|
76
114
|
|
|
77
|
-
|
|
115
|
+
You can nest styles in `light` and `dark` keys:
|
|
78
116
|
|
|
79
117
|
```
|
|
80
|
-
|
|
118
|
+
light:
|
|
119
|
+
background: "#fffaf5"
|
|
120
|
+
color: "#1a1a2e"
|
|
121
|
+
h1: { color: "#c0392b" }
|
|
122
|
+
dark:
|
|
123
|
+
background: "#1a1520"
|
|
124
|
+
color: "#e7e5e2"
|
|
125
|
+
h1: { color: "#ef6f5e" }
|
|
81
126
|
```
|
|
82
127
|
|
|
83
|
-
|
|
128
|
+
These will be used when you view the site in each mode.
|
|
84
129
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
The `mode` parameter controls which view opens. Valid values are `read` (clean reading view, style panel hidden), `style` (style panel visible), and `raw` (raw markdown editor). When sharing a link for someone to read, use `mode=read`:
|
|
130
|
+
Non-color properties (fonts, sizes, spacing) are shared across themes and live at the top level.
|
|
88
131
|
|
|
89
|
-
|
|
90
|
-
https://sdocs.dev/#md=...&mode=read
|
|
91
|
-
```
|
|
132
|
+
All color controls are in the **Colors** section of the style panel. The light/dark toggle at the top of that section lets you customize each theme independently. Colors cascade from general to specific — set `color` once and it flows to headings, paragraphs, and lists unless you override them individually.
|
|
92
133
|
|
|
93
|
-
|
|
134
|
+
### Drag & drop
|
|
94
135
|
|
|
95
|
-
|
|
96
|
-
https://sdocs.dev/#md=...&sec=url-formatting
|
|
97
|
-
```
|
|
136
|
+
Drag any `.md` file onto the editor to SmallDoc it instantly.
|
|
98
137
|
|
|
99
|
-
|
|
138
|
+
### Exports
|
|
100
139
|
|
|
101
|
-
|
|
140
|
+
SmallDocs can export your document in four formats:
|
|
102
141
|
|
|
103
|
-
|
|
142
|
+
- **Raw .md** — your markdown content with all front matter stripped. Plain markdown, compatible with anything.
|
|
143
|
+
- **PDF** — a styled PDF generated from the rendered view via the browser's print engine.
|
|
144
|
+
- **Word (.docx)** — a styled Word document generated from the rendered HTML.
|
|
145
|
+
- **Styled .md** — your markdown with the `styles:` front matter block included. This is the format SmallDocs reads back in, so your formatting is preserved.
|
|
104
146
|
|
|
105
|
-
|
|
147
|
+
### Collapsed headers
|
|
106
148
|
|
|
107
|
-
|
|
149
|
+
SmallDocs loads with all headers collapsed. This is done because it makes it easy to get an overview of the whole document.
|
|
108
150
|
|
|
109
|
-
|
|
151
|
+
If you expand a parent, all of its children expand too.
|
|
110
152
|
|
|
111
|
-
|
|
153
|
+
### Copy & paste
|
|
112
154
|
|
|
113
|
-
|
|
155
|
+
Every header has its own copy and paste button. This copies its content and all of its children's content. At the moment this is the fastest way to get SmallDoc content into your agent's context, but we're looking for novel ideas to make this better.
|
|
114
156
|
|
|
115
|
-
|
|
157
|
+
### Works offline
|
|
116
158
|
|
|
117
|
-
|
|
159
|
+
`https://sdocs.dev` uses extensive client side caching. If you've loaded the site once, you can visit it even when you're offline. If something has changed server side, we invalidate the cache and the next time you visit the site you'll get the latest version.
|
|
118
160
|
|
|
119
|
-
|
|
161
|
+
### Auto-save
|
|
120
162
|
|
|
121
|
-
|
|
122
|
-
- **PDF** — a styled PDF generated from the rendered view via the browser's print engine.
|
|
123
|
-
- **Word (.docx)** — a styled Word document generated from the rendered HTML.
|
|
124
|
-
- **Styled .md** — your markdown with the `styles:` front matter block included. This is the format SmallDocs reads back in, so your formatting is preserved.
|
|
163
|
+
Because the URL includes your full document and dynamically updates via JavaScript, every change you make is instantly preserved in the URL. This works when you're offline.
|
|
125
164
|
|
|
126
165
|
## The CLI
|
|
127
166
|
|
|
167
|
+
### Installation
|
|
168
|
+
|
|
128
169
|
SmallDocs has a command-line tool that lets you open, share, and style markdown files from the terminal. Install it once:
|
|
129
170
|
|
|
130
171
|
```
|
|
@@ -141,28 +182,47 @@ sdoc README.md
|
|
|
141
182
|
|
|
142
183
|
Your browser opens with the document styled and readable. That's it — one command to go from `.md` file to formatted document.
|
|
143
184
|
|
|
144
|
-
###
|
|
185
|
+
### Share a link
|
|
145
186
|
|
|
146
|
-
|
|
187
|
+
```
|
|
188
|
+
sdoc share README.md
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
This copies a shareable link to your clipboard.
|
|
192
|
+
|
|
193
|
+
You can also combine it with options:
|
|
147
194
|
|
|
148
195
|
```
|
|
149
|
-
sdoc
|
|
150
|
-
sdoc
|
|
151
|
-
sdoc
|
|
152
|
-
sdoc README.md --raw # raw mode (plain markdown source)
|
|
196
|
+
sdoc share report.md --section "Results" # deep-link to a heading
|
|
197
|
+
sdoc share notes.md --write # link opens in write mode
|
|
198
|
+
sdoc share notes.md --dark # link opens in dark theme
|
|
153
199
|
```
|
|
154
200
|
|
|
155
|
-
###
|
|
201
|
+
### Start a new document
|
|
156
202
|
|
|
157
203
|
```
|
|
158
|
-
sdoc
|
|
204
|
+
sdoc new
|
|
159
205
|
```
|
|
160
206
|
|
|
161
|
-
|
|
207
|
+
Opens a blank document in write mode, ready to type a `h1`.
|
|
208
|
+
|
|
209
|
+
### Style schema
|
|
162
210
|
|
|
163
211
|
```
|
|
164
|
-
sdoc
|
|
165
|
-
|
|
212
|
+
sdoc schema
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Prints every available style property with its type, default value, and description. This is designed to be readable by both humans and LLMs — so your agent can write YAML front matter for you.
|
|
216
|
+
|
|
217
|
+
### Modes
|
|
218
|
+
|
|
219
|
+
By default, files open in read mode. You can open in any mode:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
sdoc README.md # read mode (default)
|
|
223
|
+
sdoc README.md --write # write mode (contentEditable editor)
|
|
224
|
+
sdoc README.md --style # style mode (styling panel visible)
|
|
225
|
+
sdoc README.md --raw # raw mode (plain markdown source)
|
|
166
226
|
```
|
|
167
227
|
|
|
168
228
|
### Pipe from stdin
|
|
@@ -175,14 +235,6 @@ cat notes.md | sdoc share # pipe to clipboard link
|
|
|
175
235
|
your-agent --output md | sdoc # pipe agent output to browser
|
|
176
236
|
```
|
|
177
237
|
|
|
178
|
-
### Start a new document
|
|
179
|
-
|
|
180
|
-
```
|
|
181
|
-
sdoc new
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
Opens a blank document in write mode, ready to type.
|
|
185
|
-
|
|
186
238
|
### Default styles
|
|
187
239
|
|
|
188
240
|
If you find a style you like, use the "Save as Default" panel in the Style view to generate a command that saves your preferences to `~/.sdocs/styles.yaml`. The CLI automatically applies these defaults to every file you open — unless the file has its own styles, which always take priority.
|
|
@@ -192,14 +244,6 @@ sdoc defaults # view your current defaults
|
|
|
192
244
|
sdoc defaults --reset # remove them
|
|
193
245
|
```
|
|
194
246
|
|
|
195
|
-
### Style schema
|
|
196
|
-
|
|
197
|
-
```
|
|
198
|
-
sdoc schema
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
Prints every available style property with its type, default value, and description. This is designed to be readable by both humans and LLMs — so your agent can write YAML front matter for you.
|
|
202
|
-
|
|
203
247
|
### For agents
|
|
204
248
|
|
|
205
249
|
The CLI is designed to work well in automated workflows. A few patterns:
|
|
@@ -209,18 +253,3 @@ The CLI is designed to work well in automated workflows. A few patterns:
|
|
|
209
253
|
- **Deep-link to context**: `sdoc share file.md --section "Heading"` creates a URL that scrolls straight to the relevant section
|
|
210
254
|
- **No auth, no API keys**: everything is client-side — the URL *is* the document
|
|
211
255
|
|
|
212
|
-
### Small opinionated things
|
|
213
|
-
|
|
214
|
-
SmallDocs has opinions. We do some things which might not work for everyone but hopefully make the general `.md` experience better for most.
|
|
215
|
-
|
|
216
|
-
We welcome your opinions. Raise an issue on GitHub or make a pull request if you want something to change.
|
|
217
|
-
|
|
218
|
-
#### Collapsed headers
|
|
219
|
-
|
|
220
|
-
SmallDocs loads with all headers collapsed. This is done because it makes it easy to get an overview of the whole document.
|
|
221
|
-
|
|
222
|
-
If you expand a parent, all of its children expand too.
|
|
223
|
-
|
|
224
|
-
#### Copy & paste
|
|
225
|
-
|
|
226
|
-
Every header has its own copy and paste button. This copies its content and all of its children's content. At the moment this is the fastest way to get SmallDoc content into your agent's context, but we're looking for novel ideas to make this better.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|