sdocs-dev 1.0.0 → 1.1.0
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/bin/sdocs-dev.js +69 -13
- package/package.json +1 -1
- package/public/css/layout.css +1 -0
- package/public/css/panel.css +45 -0
- package/public/css/rendered.css +7 -1
- package/public/css/tokens.css +1 -0
- package/public/default.md +100 -3
- package/public/index.html +103 -50
- package/public/sdocs-app.js +10 -0
- package/public/sdocs-controls.js +72 -8
- package/public/sdocs-export.js +26 -8
- package/public/sdocs-state.js +19 -3
- package/public/sdocs-styles.js +271 -5
- package/public/sdocs-theme.js +92 -6
- package/public/sdocs-yaml.js +45 -22
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 #
|
|
6
|
+
* sdoc share report.md # shareable URL (copied 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
|
|
@@ -29,14 +29,14 @@ USAGE
|
|
|
29
29
|
sdoc <file> --style Open with style panel
|
|
30
30
|
sdoc <file> --raw Open raw markdown source
|
|
31
31
|
sdoc new New blank document (write mode)
|
|
32
|
-
sdoc share <file>
|
|
32
|
+
sdoc share <file> Shareable URL (copied to clipboard)
|
|
33
33
|
sdoc share <file> --section "X" URL with section anchor
|
|
34
34
|
sdoc schema Print the full styles schema
|
|
35
35
|
sdoc defaults Show ~/.sdocs/styles.yaml
|
|
36
36
|
sdoc defaults --reset Remove default styles
|
|
37
37
|
sdoc help Show this help
|
|
38
38
|
cat file.md | sdoc Pipe markdown from stdin
|
|
39
|
-
cat file.md | sdoc share Pipe to shareable URL
|
|
39
|
+
cat file.md | sdoc share Pipe to shareable URL (clipboard)
|
|
40
40
|
|
|
41
41
|
MODE FLAGS
|
|
42
42
|
--read Clean reading view (default when file given)
|
|
@@ -83,6 +83,8 @@ GENERAL
|
|
|
83
83
|
Default: "Inter"
|
|
84
84
|
baseFontSize number Base font size in px. All rem/em values scale from this.
|
|
85
85
|
Default: 16
|
|
86
|
+
background string Page background color (hex).
|
|
87
|
+
Default: "#ffffff" (light) / "#1c1a17" (dark)
|
|
86
88
|
color string Master body text color (hex). Cascades to headings,
|
|
87
89
|
paragraphs, and lists unless those are overridden.
|
|
88
90
|
Default: "#1c1917"
|
|
@@ -141,6 +143,30 @@ COLOR CASCADE
|
|
|
141
143
|
color → p.color → list.color
|
|
142
144
|
Set a child color only when you want it to differ from its parent.
|
|
143
145
|
|
|
146
|
+
THEME COLORS
|
|
147
|
+
Colors can be set per-theme using \`light:\` and \`dark:\` sub-blocks under \`styles:\`.
|
|
148
|
+
Non-color properties (fonts, sizes, spacing, weights) remain at the top level
|
|
149
|
+
and are shared across both themes.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
styles:
|
|
153
|
+
fontFamily: Lora
|
|
154
|
+
baseFontSize: 17
|
|
155
|
+
light:
|
|
156
|
+
background: "#ffffff"
|
|
157
|
+
color: "#1a1a2e"
|
|
158
|
+
h1: { color: "#c0392b" }
|
|
159
|
+
link: { color: "#2563eb" }
|
|
160
|
+
dark:
|
|
161
|
+
background: "#1c1a17"
|
|
162
|
+
color: "#e7e5e2"
|
|
163
|
+
h1: { color: "#ef6f5e" }
|
|
164
|
+
link: { color: "#60a5fa" }
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
Top-level colors (old format) are treated as light-theme colors for
|
|
168
|
+
backwards compatibility.
|
|
169
|
+
|
|
144
170
|
FONTS (24 supported, loaded lazily from Google Fonts)
|
|
145
171
|
Inter · Roboto · Open Sans · Lato · Montserrat · Source Sans 3
|
|
146
172
|
Oswald · Raleway · Poppins · Merriweather · Ubuntu · Nunito
|
|
@@ -152,15 +178,28 @@ EXAMPLE — editorial article with colored heading tiers
|
|
|
152
178
|
styles:
|
|
153
179
|
fontFamily: Lora
|
|
154
180
|
baseFontSize: 17
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
h1: { fontSize: 2.3, color: "#c0392b", fontWeight: 700 }
|
|
159
|
-
h2: { fontSize: 1.55, color: "#8e44ad", fontWeight: 600 }
|
|
160
|
-
h3: { fontSize: 1.2, color: "#16a085", fontWeight: 600 }
|
|
181
|
+
h1: { fontSize: 2.3, fontWeight: 700 }
|
|
182
|
+
h2: { fontSize: 1.55, fontWeight: 600 }
|
|
183
|
+
h3: { fontSize: 1.2, fontWeight: 600 }
|
|
161
184
|
p: { lineHeight: 1.9, marginBottom: 1.2 }
|
|
162
|
-
|
|
163
|
-
|
|
185
|
+
light:
|
|
186
|
+
background: "#fffaf5"
|
|
187
|
+
color: "#1a1a2e"
|
|
188
|
+
headers: { color: "#2c3e50" }
|
|
189
|
+
h1: { color: "#c0392b" }
|
|
190
|
+
h2: { color: "#8e44ad" }
|
|
191
|
+
h3: { color: "#16a085" }
|
|
192
|
+
link: { color: "#e67e22", decoration: "underline" }
|
|
193
|
+
blockquote: { borderColor: "#c0392b", borderWidth: 4, color: "#7f8c8d" }
|
|
194
|
+
dark:
|
|
195
|
+
background: "#1a1520"
|
|
196
|
+
color: "#e7e5e2"
|
|
197
|
+
headers: { color: "#b0aaa5" }
|
|
198
|
+
h1: { color: "#ef6f5e" }
|
|
199
|
+
h2: { color: "#c490e4" }
|
|
200
|
+
h3: { color: "#5ed4b8" }
|
|
201
|
+
link: { color: "#f0a860", decoration: "underline" }
|
|
202
|
+
blockquote: { borderColor: "#ef6f5e", borderWidth: 4, color: "#9e9590" }
|
|
164
203
|
---
|
|
165
204
|
`;
|
|
166
205
|
|
|
@@ -310,13 +349,23 @@ function resetDefaults() {
|
|
|
310
349
|
}
|
|
311
350
|
|
|
312
351
|
// Deep merge: defaults under file styles (file wins on conflict)
|
|
352
|
+
// Recursive for light:/dark: sub-objects that contain nested objects
|
|
313
353
|
function mergeStyles(defaults, fileStyles) {
|
|
314
354
|
if (!defaults) return fileStyles || {};
|
|
315
355
|
if (!fileStyles) return { ...defaults };
|
|
316
356
|
const merged = { ...defaults };
|
|
317
357
|
for (const [k, v] of Object.entries(fileStyles)) {
|
|
318
358
|
if (typeof v === 'object' && v !== null && typeof merged[k] === 'object' && merged[k] !== null) {
|
|
319
|
-
|
|
359
|
+
// Recurse one level deeper for light/dark blocks that contain nested objects (e.g. h1: { color: ... })
|
|
360
|
+
const inner = { ...merged[k] };
|
|
361
|
+
for (const [ik, iv] of Object.entries(v)) {
|
|
362
|
+
if (typeof iv === 'object' && iv !== null && typeof inner[ik] === 'object' && inner[ik] !== null) {
|
|
363
|
+
inner[ik] = { ...inner[ik], ...iv };
|
|
364
|
+
} else {
|
|
365
|
+
inner[ik] = iv;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
merged[k] = inner;
|
|
320
369
|
} else {
|
|
321
370
|
merged[k] = v;
|
|
322
371
|
}
|
|
@@ -412,9 +461,16 @@ if (require.main === module) {
|
|
|
412
461
|
section: opts.section,
|
|
413
462
|
});
|
|
414
463
|
|
|
415
|
-
// Share: print URL
|
|
464
|
+
// Share: print URL and copy to clipboard
|
|
416
465
|
if (opts.subcommand === 'share') {
|
|
417
466
|
process.stdout.write(url + '\n');
|
|
467
|
+
try {
|
|
468
|
+
const clip = process.platform === 'darwin' ? 'pbcopy'
|
|
469
|
+
: execSync('which xclip 2>/dev/null', { encoding: 'utf-8' }).trim() ? 'xclip -selection clipboard'
|
|
470
|
+
: 'xsel --clipboard --input';
|
|
471
|
+
execSync(clip, { input: url, stdio: ['pipe', 'ignore', 'ignore'] });
|
|
472
|
+
console.error('Copied to clipboard.');
|
|
473
|
+
} catch (_) {}
|
|
418
474
|
process.exit(0);
|
|
419
475
|
}
|
|
420
476
|
|
package/package.json
CHANGED
package/public/css/layout.css
CHANGED
package/public/css/panel.css
CHANGED
|
@@ -38,6 +38,51 @@ body.style-mode #right {
|
|
|
38
38
|
flex-shrink: 0;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/* ── Theme tabs ── */
|
|
42
|
+
#theme-tabs {
|
|
43
|
+
margin-bottom: 10px;
|
|
44
|
+
}
|
|
45
|
+
#theme-tabs .toggle-group {
|
|
46
|
+
display: flex;
|
|
47
|
+
width: 100%;
|
|
48
|
+
gap: 0;
|
|
49
|
+
background: var(--bg-surface);
|
|
50
|
+
border: 1px solid var(--border);
|
|
51
|
+
border-radius: var(--radius);
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
}
|
|
54
|
+
#theme-tabs .btn {
|
|
55
|
+
flex: 1;
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
justify-content: center;
|
|
59
|
+
gap: 4px;
|
|
60
|
+
padding: 5px 0;
|
|
61
|
+
border: none;
|
|
62
|
+
background: none;
|
|
63
|
+
font-family: var(--font-ui);
|
|
64
|
+
font-size: 11.5px;
|
|
65
|
+
font-weight: 500;
|
|
66
|
+
color: var(--text-3);
|
|
67
|
+
cursor: pointer;
|
|
68
|
+
transition: background .12s, color .12s;
|
|
69
|
+
}
|
|
70
|
+
#theme-tabs .btn:hover {
|
|
71
|
+
background: var(--bg-hover);
|
|
72
|
+
color: var(--text-2);
|
|
73
|
+
}
|
|
74
|
+
#theme-tabs .btn.active {
|
|
75
|
+
background: var(--bg-panel);
|
|
76
|
+
color: var(--text);
|
|
77
|
+
font-weight: 600;
|
|
78
|
+
box-shadow: var(--shadow-sm);
|
|
79
|
+
}
|
|
80
|
+
#theme-tabs .btn svg {
|
|
81
|
+
width: 13px;
|
|
82
|
+
height: 13px;
|
|
83
|
+
flex-shrink: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
41
86
|
/* ── Panel sections ── */
|
|
42
87
|
.panel-section { border-bottom: 1px solid var(--border-subtle); }
|
|
43
88
|
|
package/public/css/rendered.css
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
max-width: 760px;
|
|
7
7
|
margin: 0 auto;
|
|
8
8
|
|
|
9
|
+
--md-bg: #FFFFFF;
|
|
9
10
|
--md-font-family: 'Inter', system-ui, sans-serif;
|
|
10
11
|
--md-base-size: 16px;
|
|
11
12
|
--md-line-height: 1.75;
|
|
@@ -55,6 +56,7 @@
|
|
|
55
56
|
font-size: var(--md-base-size);
|
|
56
57
|
color: var(--md-color);
|
|
57
58
|
line-height: var(--md-line-height);
|
|
59
|
+
background-color: var(--md-bg);
|
|
58
60
|
}
|
|
59
61
|
:is(#rendered, #write) h1 {
|
|
60
62
|
font-family: var(--md-h-font-family);
|
|
@@ -130,9 +132,13 @@
|
|
|
130
132
|
#rendered .section-toggle:hover {
|
|
131
133
|
color: var(--text-2);
|
|
132
134
|
}
|
|
133
|
-
#rendered .md-section-body:not(.open) > :not(.md-section) {
|
|
135
|
+
#rendered .md-section-body:not(.open):not(:has(.md-section-body.open)) > :not(.md-section) {
|
|
134
136
|
display: none;
|
|
135
137
|
}
|
|
138
|
+
#rendered .md-section-body:not(.open):has(.md-section-body.open) > :not(.md-section) {
|
|
139
|
+
opacity: 0.55;
|
|
140
|
+
padding-left: 1.2em;
|
|
141
|
+
}
|
|
136
142
|
#rendered .md-section-body:not(.open) > .md-section:not(:has(> .md-section-body.open)) {
|
|
137
143
|
padding-left: 1.2em;
|
|
138
144
|
}
|
package/public/css/tokens.css
CHANGED
package/public/default.md
CHANGED
|
@@ -18,7 +18,7 @@ styles:
|
|
|
18
18
|
|
|
19
19
|
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
20
|
|
|
21
|
-
|
|
21
|
+
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
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**, **share**, **format** and **export** `.md` files.
|
|
24
24
|
|
|
@@ -51,12 +51,23 @@ SDocs uses a `styles:` key with CSS properties written beneath it in YAML:
|
|
|
51
51
|
styles:
|
|
52
52
|
fontFamily: Lora
|
|
53
53
|
baseFontSize: 17
|
|
54
|
-
|
|
55
|
-
h1: { fontSize: 2.3, color: "#c0392b", fontWeight: 700 }
|
|
54
|
+
h1: { fontSize: 2.3, fontWeight: 700 }
|
|
56
55
|
p: { lineHeight: 1.9, marginBottom: 1.2 }
|
|
56
|
+
light:
|
|
57
|
+
background: "#fffaf5"
|
|
58
|
+
color: "#1a1a2e"
|
|
59
|
+
h1: { color: "#c0392b" }
|
|
60
|
+
dark:
|
|
61
|
+
background: "#1a1520"
|
|
62
|
+
color: "#e7e5e2"
|
|
63
|
+
h1: { color: "#ef6f5e" }
|
|
57
64
|
---
|
|
58
65
|
```
|
|
59
66
|
|
|
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
|
+
|
|
60
71
|
(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`.)
|
|
61
72
|
|
|
62
73
|
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.
|
|
@@ -112,6 +123,92 @@ SmallDocs can export your document in four formats:
|
|
|
112
123
|
- **Word (.docx)** — a styled Word document generated from the rendered HTML.
|
|
113
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.
|
|
114
125
|
|
|
126
|
+
## The CLI
|
|
127
|
+
|
|
128
|
+
SmallDocs has a command-line tool that lets you open, share, and style markdown files from the terminal. Install it once:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
npm i -g sdocs-dev
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
This gives you the `sdoc` command.
|
|
135
|
+
|
|
136
|
+
### Open a file
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
sdoc README.md
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Your browser opens with the document styled and readable. That's it — one command to go from `.md` file to formatted document.
|
|
143
|
+
|
|
144
|
+
### Modes
|
|
145
|
+
|
|
146
|
+
By default, files open in read mode. You can open in any mode:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
sdoc README.md # read mode (default)
|
|
150
|
+
sdoc README.md --write # write mode (contentEditable editor)
|
|
151
|
+
sdoc README.md --style # style mode (styling panel visible)
|
|
152
|
+
sdoc README.md --raw # raw mode (plain markdown source)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Share a link
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
sdoc share README.md
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
This prints a shareable URL and copies it to your clipboard — no browser opens. The entire document is compressed into the URL hash, so there's nothing to host or upload. Pipe it wherever you need it:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
sdoc share report.md --section "Results" # deep-link to a heading
|
|
165
|
+
sdoc share notes.md --write # link opens in write mode
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Pipe from stdin
|
|
169
|
+
|
|
170
|
+
Any command that outputs markdown can be piped directly into SmallDocs:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
cat notes.md | sdoc # open in browser
|
|
174
|
+
cat notes.md | sdoc share # get a shareable URL
|
|
175
|
+
your-agent --output md | sdoc # pipe agent output to browser
|
|
176
|
+
```
|
|
177
|
+
|
|
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
|
+
### Default styles
|
|
187
|
+
|
|
188
|
+
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.
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
sdoc defaults # view your current defaults
|
|
192
|
+
sdoc defaults --reset # remove them
|
|
193
|
+
```
|
|
194
|
+
|
|
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
|
+
### For agents
|
|
204
|
+
|
|
205
|
+
The CLI is designed to work well in automated workflows. A few patterns:
|
|
206
|
+
|
|
207
|
+
- **Generate a styled doc**: have your agent write a `.md` file with YAML front matter, then `sdoc share file.md` to get a URL
|
|
208
|
+
- **Learn the format**: `sdoc schema` gives your agent everything it needs to know about available style properties
|
|
209
|
+
- **Deep-link to context**: `sdoc share file.md --section "Heading"` creates a URL that scrolls straight to the relevant section
|
|
210
|
+
- **No auth, no API keys**: everything is client-side — the URL *is* the document
|
|
211
|
+
|
|
115
212
|
### Small opinionated things
|
|
116
213
|
|
|
117
214
|
SmallDocs has opinions. We do some things which might not work for everyone but hopefully make the general `.md` experience better for most.
|
package/public/index.html
CHANGED
|
@@ -145,11 +145,112 @@
|
|
|
145
145
|
<input type="range" id="ctrl-line-height-range" min="1.0" max="2.5" step="0.05" value="1.75">
|
|
146
146
|
<input type="number" id="ctrl-line-height-num" min="1.0" max="2.5" step="0.05" value="1.75">
|
|
147
147
|
</div>
|
|
148
|
+
<button class="factory-reset-btn" id="factory-reset-styles" title="Reset all styles to defaults">Reset all styles</button>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<!-- COLORS -->
|
|
153
|
+
<div class="panel-section">
|
|
154
|
+
<div class="panel-header" data-target="body-colors">
|
|
155
|
+
<span class="arrow">
|
|
156
|
+
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M3 2l4 3-4 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
157
|
+
</span>
|
|
158
|
+
Colors
|
|
159
|
+
</div>
|
|
160
|
+
<div class="panel-body" id="body-colors">
|
|
161
|
+
<div id="theme-tabs">
|
|
162
|
+
<div class="toggle-group">
|
|
163
|
+
<button class="btn active" id="theme-tab-light">
|
|
164
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
|
|
165
|
+
Light
|
|
166
|
+
</button>
|
|
167
|
+
<button class="btn" id="theme-tab-dark">
|
|
168
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/></svg>
|
|
169
|
+
Dark
|
|
170
|
+
</button>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
<div class="control-row">
|
|
174
|
+
<label>Background</label>
|
|
175
|
+
<div class="color-pair"><input type="color" id="ctrl-bg-color" value="#ffffff"><button class="reset-btn" id="reset-bg-color" title="Reset to default">↺</button></div>
|
|
176
|
+
</div>
|
|
148
177
|
<div class="control-row">
|
|
149
|
-
<label>Text
|
|
178
|
+
<label>Text</label>
|
|
150
179
|
<div class="color-pair"><input type="color" id="ctrl-color" value="#1c1917"><button class="reset-btn" id="reset-color" title="Reset to default">↺</button></div>
|
|
151
180
|
</div>
|
|
152
|
-
|
|
181
|
+
|
|
182
|
+
<div class="sub-section">
|
|
183
|
+
<div class="sub-header" data-target="sub-colors-headings">
|
|
184
|
+
<span class="arrow">▶</span> Headings
|
|
185
|
+
</div>
|
|
186
|
+
<div class="sub-body" id="sub-colors-headings">
|
|
187
|
+
<div class="control-row">
|
|
188
|
+
<label>All</label>
|
|
189
|
+
<div class="color-pair"><input type="color" id="ctrl-h-color" value="#1c1917"><button class="reset-btn" id="reset-h-color" title="Follow body text color">↺</button></div>
|
|
190
|
+
</div>
|
|
191
|
+
<div class="control-row">
|
|
192
|
+
<label>H1</label>
|
|
193
|
+
<div class="color-pair"><input type="color" id="ctrl-h1-color" value="#1c1917"><button class="reset-btn" id="reset-h1-color" title="Follow header color">↺</button></div>
|
|
194
|
+
</div>
|
|
195
|
+
<div class="control-row">
|
|
196
|
+
<label>H2</label>
|
|
197
|
+
<div class="color-pair"><input type="color" id="ctrl-h2-color" value="#1c1917"><button class="reset-btn" id="reset-h2-color" title="Follow header color">↺</button></div>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="control-row">
|
|
200
|
+
<label>H3</label>
|
|
201
|
+
<div class="color-pair"><input type="color" id="ctrl-h3-color" value="#1c1917"><button class="reset-btn" id="reset-h3-color" title="Follow header color">↺</button></div>
|
|
202
|
+
</div>
|
|
203
|
+
<div class="control-row">
|
|
204
|
+
<label>H4</label>
|
|
205
|
+
<div class="color-pair"><input type="color" id="ctrl-h4-color" value="#1c1917"><button class="reset-btn" id="reset-h4-color" title="Follow header color">↺</button></div>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<div class="control-row">
|
|
211
|
+
<label>Paragraph</label>
|
|
212
|
+
<div class="color-pair"><input type="color" id="ctrl-p-color" value="#1c1917"><button class="reset-btn" id="reset-p-color" title="Follow body text color">↺</button></div>
|
|
213
|
+
</div>
|
|
214
|
+
<div class="control-row">
|
|
215
|
+
<label>Lists</label>
|
|
216
|
+
<div class="color-pair"><input type="color" id="ctrl-list-color" value="#1c1917"><button class="reset-btn" id="reset-list-color" title="Follow paragraph color">↺</button></div>
|
|
217
|
+
</div>
|
|
218
|
+
<div class="control-row">
|
|
219
|
+
<label>Link</label>
|
|
220
|
+
<div class="color-pair"><input type="color" id="ctrl-link-color" value="#2563eb"><button class="reset-btn" id="reset-link-color" title="Reset to default">↺</button></div>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<div class="sub-section">
|
|
224
|
+
<div class="sub-header" data-target="sub-colors-code">
|
|
225
|
+
<span class="arrow">▶</span> Code
|
|
226
|
+
</div>
|
|
227
|
+
<div class="sub-body" id="sub-colors-code">
|
|
228
|
+
<div class="control-row">
|
|
229
|
+
<label>Background</label>
|
|
230
|
+
<div class="color-pair"><input type="color" id="ctrl-code-bg" value="#f4f1ed"><button class="reset-btn" id="reset-code-bg" title="Reset to default">↺</button></div>
|
|
231
|
+
</div>
|
|
232
|
+
<div class="control-row">
|
|
233
|
+
<label>Text</label>
|
|
234
|
+
<div class="color-pair"><input type="color" id="ctrl-code-color" value="#6b21a8"><button class="reset-btn" id="reset-code-color" title="Reset to default">↺</button></div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
<div class="sub-section">
|
|
240
|
+
<div class="sub-header" data-target="sub-colors-bq">
|
|
241
|
+
<span class="arrow">▶</span> Blockquote
|
|
242
|
+
</div>
|
|
243
|
+
<div class="sub-body" id="sub-colors-bq">
|
|
244
|
+
<div class="control-row">
|
|
245
|
+
<label>Border</label>
|
|
246
|
+
<div class="color-pair"><input type="color" id="ctrl-bq-border-color" value="#2563eb"><button class="reset-btn" id="reset-bq-border-color" title="Reset to default">↺</button></div>
|
|
247
|
+
</div>
|
|
248
|
+
<div class="control-row">
|
|
249
|
+
<label>Text</label>
|
|
250
|
+
<div class="color-pair"><input type="color" id="ctrl-bq-color" value="#6b6560"><button class="reset-btn" id="reset-bq-color" title="Reset to default">↺</button></div>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
153
254
|
</div>
|
|
154
255
|
</div>
|
|
155
256
|
|
|
@@ -183,10 +284,6 @@
|
|
|
183
284
|
<input type="number" id="ctrl-h-mb-num" min="0" max="2" step="0.1" value="0.4">
|
|
184
285
|
<span class="unit">em</span>
|
|
185
286
|
</div>
|
|
186
|
-
<div class="control-row">
|
|
187
|
-
<label>Color</label>
|
|
188
|
-
<div class="color-pair"><input type="color" id="ctrl-h-color" value="#1c1917"><button class="reset-btn" id="reset-h-color" title="Follow body text color">↺</button></div>
|
|
189
|
-
</div>
|
|
190
287
|
</div>
|
|
191
288
|
</div>
|
|
192
289
|
|
|
@@ -201,10 +298,6 @@
|
|
|
201
298
|
<input type="number" id="ctrl-h1-size-num" min="1" max="4" step="0.1" value="2.1">
|
|
202
299
|
<span class="unit">em</span>
|
|
203
300
|
</div>
|
|
204
|
-
<div class="control-row">
|
|
205
|
-
<label>Color</label>
|
|
206
|
-
<div class="color-pair"><input type="color" id="ctrl-h1-color" value="#1c1917"><button class="reset-btn" id="reset-h1-color" title="Follow header color">↺</button></div>
|
|
207
|
-
</div>
|
|
208
301
|
<div class="control-row">
|
|
209
302
|
<label>Weight</label>
|
|
210
303
|
<select id="ctrl-h1-weight">
|
|
@@ -229,10 +322,6 @@
|
|
|
229
322
|
<input type="number" id="ctrl-h2-size-num" min="1" max="3" step="0.1" value="1.55">
|
|
230
323
|
<span class="unit">em</span>
|
|
231
324
|
</div>
|
|
232
|
-
<div class="control-row">
|
|
233
|
-
<label>Color</label>
|
|
234
|
-
<div class="color-pair"><input type="color" id="ctrl-h2-color" value="#1c1917"><button class="reset-btn" id="reset-h2-color" title="Follow header color">↺</button></div>
|
|
235
|
-
</div>
|
|
236
325
|
<div class="control-row">
|
|
237
326
|
<label>Weight</label>
|
|
238
327
|
<select id="ctrl-h2-weight">
|
|
@@ -256,10 +345,6 @@
|
|
|
256
345
|
<input type="number" id="ctrl-h3-size-num" min="0.8" max="2.5" step="0.1" value="1.2">
|
|
257
346
|
<span class="unit">em</span>
|
|
258
347
|
</div>
|
|
259
|
-
<div class="control-row">
|
|
260
|
-
<label>Color</label>
|
|
261
|
-
<div class="color-pair"><input type="color" id="ctrl-h3-color" value="#1c1917"><button class="reset-btn" id="reset-h3-color" title="Follow header color">↺</button></div>
|
|
262
|
-
</div>
|
|
263
348
|
<div class="control-row">
|
|
264
349
|
<label>Weight</label>
|
|
265
350
|
<select id="ctrl-h3-weight">
|
|
@@ -283,10 +368,6 @@
|
|
|
283
368
|
<input type="number" id="ctrl-h4-size-num" min="0.8" max="2" step="0.1" value="1.0">
|
|
284
369
|
<span class="unit">em</span>
|
|
285
370
|
</div>
|
|
286
|
-
<div class="control-row">
|
|
287
|
-
<label>Color</label>
|
|
288
|
-
<div class="color-pair"><input type="color" id="ctrl-h4-color" value="#1c1917"><button class="reset-btn" id="reset-h4-color" title="Follow header color">↺</button></div>
|
|
289
|
-
</div>
|
|
290
371
|
<div class="control-row">
|
|
291
372
|
<label>Weight</label>
|
|
292
373
|
<select id="ctrl-h4-weight">
|
|
@@ -311,10 +392,6 @@
|
|
|
311
392
|
Paragraph
|
|
312
393
|
</div>
|
|
313
394
|
<div class="panel-body" id="body-paragraph">
|
|
314
|
-
<div class="control-row">
|
|
315
|
-
<label>Color</label>
|
|
316
|
-
<div class="color-pair"><input type="color" id="ctrl-p-color" value="#1c1917"><button class="reset-btn" id="reset-p-color" title="Follow body text color">↺</button></div>
|
|
317
|
-
</div>
|
|
318
395
|
<div class="control-row">
|
|
319
396
|
<label>Line height</label>
|
|
320
397
|
<input type="range" id="ctrl-p-lh-range" min="1.0" max="2.5" step="0.05" value="1.75">
|
|
@@ -338,10 +415,6 @@
|
|
|
338
415
|
Lists
|
|
339
416
|
</div>
|
|
340
417
|
<div class="panel-body" id="body-lists">
|
|
341
|
-
<div class="control-row">
|
|
342
|
-
<label>Color</label>
|
|
343
|
-
<div class="color-pair"><input type="color" id="ctrl-list-color" value="#1c1917"><button class="reset-btn" id="reset-list-color" title="Follow paragraph color">↺</button></div>
|
|
344
|
-
</div>
|
|
345
418
|
<div class="control-row">
|
|
346
419
|
<label>Item spacing</label>
|
|
347
420
|
<input type="range" id="ctrl-list-spacing-range" min="0" max="1.5" step="0.05" value="0.3">
|
|
@@ -366,10 +439,6 @@
|
|
|
366
439
|
Links
|
|
367
440
|
</div>
|
|
368
441
|
<div class="panel-body" id="body-links">
|
|
369
|
-
<div class="control-row">
|
|
370
|
-
<label>Color</label>
|
|
371
|
-
<div class="color-pair"><input type="color" id="ctrl-link-color" value="#2563eb"><button class="reset-btn" id="reset-link-color" title="Reset to default">↺</button></div>
|
|
372
|
-
</div>
|
|
373
442
|
<div class="control-row">
|
|
374
443
|
<label>Decoration</label>
|
|
375
444
|
<select id="ctrl-link-decoration">
|
|
@@ -401,14 +470,6 @@
|
|
|
401
470
|
<option value="monospace">System mono</option>
|
|
402
471
|
</select>
|
|
403
472
|
</div>
|
|
404
|
-
<div class="control-row">
|
|
405
|
-
<label>Background</label>
|
|
406
|
-
<div class="color-pair"><input type="color" id="ctrl-code-bg" value="#f4f1ed"><button class="reset-btn" id="reset-code-bg" title="Reset to default">↺</button></div>
|
|
407
|
-
</div>
|
|
408
|
-
<div class="control-row">
|
|
409
|
-
<label>Text color</label>
|
|
410
|
-
<div class="color-pair"><input type="color" id="ctrl-code-color" value="#6b21a8"><button class="reset-btn" id="reset-code-color" title="Reset to default">↺</button></div>
|
|
411
|
-
</div>
|
|
412
473
|
</div>
|
|
413
474
|
</div>
|
|
414
475
|
|
|
@@ -421,10 +482,6 @@
|
|
|
421
482
|
Blockquote
|
|
422
483
|
</div>
|
|
423
484
|
<div class="panel-body" id="body-blockquote">
|
|
424
|
-
<div class="control-row">
|
|
425
|
-
<label>Border color</label>
|
|
426
|
-
<div class="color-pair"><input type="color" id="ctrl-bq-border-color" value="#2563eb"><button class="reset-btn" id="reset-bq-border-color" title="Reset to default">↺</button></div>
|
|
427
|
-
</div>
|
|
428
485
|
<div class="control-row">
|
|
429
486
|
<label>Border width</label>
|
|
430
487
|
<input type="range" id="ctrl-bq-bw-range" min="1" max="8" step="1" value="3">
|
|
@@ -437,10 +494,6 @@
|
|
|
437
494
|
<input type="number" id="ctrl-bq-size-num" min="0.7" max="1.5" step="0.05" value="1.0">
|
|
438
495
|
<span class="unit">em</span>
|
|
439
496
|
</div>
|
|
440
|
-
<div class="control-row">
|
|
441
|
-
<label>Text color</label>
|
|
442
|
-
<div class="color-pair"><input type="color" id="ctrl-bq-color" value="#6b6560"><button class="reset-btn" id="reset-bq-color" title="Reset to default">↺</button></div>
|
|
443
|
-
</div>
|
|
444
497
|
</div>
|
|
445
498
|
</div>
|
|
446
499
|
|
package/public/sdocs-app.js
CHANGED
|
@@ -147,6 +147,7 @@ function render() {
|
|
|
147
147
|
S.renderedEl.querySelectorAll('.md-section > h2, .md-section > h3, .md-section > h4').forEach(function(heading) {
|
|
148
148
|
heading.addEventListener('click', function(e) {
|
|
149
149
|
if (e.target.closest('.header-anchor') || e.target.closest('.header-copy-btn')) return;
|
|
150
|
+
var yBefore = heading.getBoundingClientRect().top;
|
|
150
151
|
var section = heading.closest('.md-section');
|
|
151
152
|
var body = section.querySelector('.md-section-body');
|
|
152
153
|
var toggle = section.querySelector('.section-toggle');
|
|
@@ -154,6 +155,10 @@ function render() {
|
|
|
154
155
|
toggle.classList.toggle('open', isOpen);
|
|
155
156
|
body.querySelectorAll('.md-section-body').forEach(function(b) { b.classList.toggle('open', isOpen); });
|
|
156
157
|
body.querySelectorAll('.section-toggle').forEach(function(t) { t.classList.toggle('open', isOpen); });
|
|
158
|
+
var yAfter = heading.getBoundingClientRect().top;
|
|
159
|
+
if (yAfter !== yBefore) {
|
|
160
|
+
contentArea.scrollTop += yAfter - yBefore;
|
|
161
|
+
}
|
|
157
162
|
});
|
|
158
163
|
});
|
|
159
164
|
}
|
|
@@ -366,6 +371,8 @@ function setMode(mode, skipHash) {
|
|
|
366
371
|
}
|
|
367
372
|
|
|
368
373
|
document.getElementById('btn-theme').addEventListener('click', function() { S.toggleTheme(); });
|
|
374
|
+
document.getElementById('theme-tab-light').addEventListener('click', function() { S.switchThemeAndUpdate('light'); });
|
|
375
|
+
document.getElementById('theme-tab-dark').addEventListener('click', function() { S.switchThemeAndUpdate('dark'); });
|
|
369
376
|
document.getElementById('btn-read').addEventListener('click', function() { setMode('read'); });
|
|
370
377
|
document.getElementById('btn-style').addEventListener('click', function() { setMode('style'); });
|
|
371
378
|
document.getElementById('btn-write').addEventListener('click', function() { setMode('write'); });
|
|
@@ -466,6 +473,9 @@ S.setMode = setMode;
|
|
|
466
473
|
S.render = render;
|
|
467
474
|
S.loadText = loadText;
|
|
468
475
|
|
|
476
|
+
// Sync theme tabs to initial theme
|
|
477
|
+
S.updateThemeTabs(S.activeTheme);
|
|
478
|
+
|
|
469
479
|
// ── Init ──────────────────────────────────
|
|
470
480
|
|
|
471
481
|
(async function () {
|