sdocs-dev 1.6.2 → 1.12.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.
@@ -69,6 +69,11 @@ const CTRL_CSS_MAP = {
69
69
  '_sd_ctrl-list-indent-num': { cssVar: '--md-list-indent', suffix: 'em' },
70
70
  '_sd_ctrl-chart-accent': { cssVar: '--md-chart-accent' },
71
71
  '_sd_ctrl-chart-palette': { cssVar: '--md-chart-palette' },
72
+ '_sd_ctrl-table-border': { cssVar: '--md-table-border' },
73
+ '_sd_ctrl-table-header-bg': { cssVar: '--md-table-header-bg' },
74
+ '_sd_ctrl-table-even-bg': { cssVar: '--md-table-even-bg' },
75
+ '_sd_ctrl-table-odd-bg': { cssVar: '--md-table-odd-bg' },
76
+ '_sd_ctrl-table-text': { cssVar: '--md-table-text' },
72
77
  };
73
78
 
74
79
  // Range ↔ Number input pairs
@@ -136,17 +141,42 @@ function hslToHex(h, s, l) {
136
141
  * Keeps hue and saturation, mirrors lightness around 50%.
137
142
  * Slightly biased: dark bgs get very dark (L≈10-20), light text gets bright (L≈80-90).
138
143
  */
144
+ // Color controls whose value is painted as TEXT rather than a fill. Their
145
+ // dark-mode counterpart must stay readable, so a very dark text color is
146
+ // lightened instead of preserved as an "intentional dark background".
147
+ var TEXT_COLOR_CONTROLS = {
148
+ '_sd_ctrl-color': 1, '_sd_ctrl-h-color': 1,
149
+ '_sd_ctrl-h1-color': 1, '_sd_ctrl-h2-color': 1,
150
+ '_sd_ctrl-h3-color': 1, '_sd_ctrl-h4-color': 1,
151
+ '_sd_ctrl-p-color': 1, '_sd_ctrl-list-color': 1,
152
+ '_sd_ctrl-link-color': 1, '_sd_ctrl-block-text': 1,
153
+ '_sd_ctrl-code-color': 1, '_sd_ctrl-bq-color': 1,
154
+ '_sd_ctrl-chart-text': 1, '_sd_ctrl-table-text': 1
155
+ };
156
+
139
157
  /**
140
- * invertLightness(hex)
158
+ * colorControlRole(ctrlId) -> 'text' | 'background'
159
+ * Tells invertLightness whether a control's color is rendered as text.
160
+ */
161
+ function colorControlRole(ctrlId) {
162
+ return TEXT_COLOR_CONTROLS[ctrlId] ? 'text' : 'background';
163
+ }
164
+
165
+ /**
166
+ * invertLightness(hex, role)
141
167
  * Generates a dark-theme counterpart for a light-theme color.
142
168
  *
143
169
  * Strategy: colors that look "right" in light mode get adapted for dark mode.
144
170
  * - Very light colors (L>65): page/block backgrounds → make very dark
145
171
  * - Very dark colors (L<20): already dark, likely intentional → keep as-is
172
+ * UNLESS role is 'text', in which case lighten it so it stays readable.
146
173
  * - Dark-ish colors (20<L<45): body text, headings → make light
147
174
  * - Mid-range (45-65): accent colors → moderate shift
175
+ *
176
+ * role: 'text' for colors painted as text (body, headings, links, code text),
177
+ * 'background' (default) for fills. Text colors are never kept dark.
148
178
  */
149
- function invertLightness(hex) {
179
+ function invertLightness(hex, role) {
150
180
  var hsl = hexToHsl(hex);
151
181
  if (!hsl) return hex;
152
182
  var h = hsl[0], s = hsl[1], l = hsl[2];
@@ -160,7 +190,7 @@ function invertLightness(hex) {
160
190
  // Light background/accent → dark
161
191
  invL = 12 + (100 - l) * 0.4; // L70→24, L65→26
162
192
  invS = s * 0.75;
163
- } else if (l < 20) {
193
+ } else if (l < 20 && role !== 'text') {
164
194
  // Already very dark → keep as-is (intentional dark bg like code blocks)
165
195
  return hex;
166
196
  } else if (l < 40) {
@@ -308,6 +338,15 @@ function collectStyles(values, overriddenColors) {
308
338
  if (overriddenColors.has('_sd_ctrl-chart-text')) chartObj.textColor = gv('_sd_ctrl-chart-text');
309
339
  if (Object.keys(chartObj).length) styles.chart = chartObj;
310
340
 
341
+ // Table styles
342
+ var tableObj = {};
343
+ if (overriddenColors.has('_sd_ctrl-table-border')) tableObj.border = gv('_sd_ctrl-table-border');
344
+ if (overriddenColors.has('_sd_ctrl-table-header-bg')) tableObj.headerBackground = gv('_sd_ctrl-table-header-bg');
345
+ if (overriddenColors.has('_sd_ctrl-table-even-bg')) tableObj.evenBackground = gv('_sd_ctrl-table-even-bg');
346
+ if (overriddenColors.has('_sd_ctrl-table-odd-bg')) tableObj.oddBackground = gv('_sd_ctrl-table-odd-bg');
347
+ if (overriddenColors.has('_sd_ctrl-table-text')) tableObj.color = gv('_sd_ctrl-table-text');
348
+ if (Object.keys(tableObj).length) styles.table = tableObj;
349
+
311
350
  return styles;
312
351
  }
313
352
 
@@ -397,6 +436,13 @@ function stylesToControls(styles) {
397
436
  if (ch.background) { controls['_sd_ctrl-chart-bg'] = ch.background; overridden.add('_sd_ctrl-chart-bg'); }
398
437
  if (ch.textColor) { controls['_sd_ctrl-chart-text'] = ch.textColor; overridden.add('_sd_ctrl-chart-text'); }
399
438
 
439
+ const tb = styles.table || {};
440
+ if (tb.border) { controls['_sd_ctrl-table-border'] = tb.border; overridden.add('_sd_ctrl-table-border'); }
441
+ if (tb.headerBackground) { controls['_sd_ctrl-table-header-bg'] = tb.headerBackground; overridden.add('_sd_ctrl-table-header-bg'); }
442
+ if (tb.evenBackground) { controls['_sd_ctrl-table-even-bg'] = tb.evenBackground; overridden.add('_sd_ctrl-table-even-bg'); }
443
+ if (tb.oddBackground) { controls['_sd_ctrl-table-odd-bg'] = tb.oddBackground; overridden.add('_sd_ctrl-table-odd-bg'); }
444
+ if (tb.color) { controls['_sd_ctrl-table-text'] = tb.color; overridden.add('_sd_ctrl-table-text'); }
445
+
400
446
  return { controls, overriddenColors: overridden };
401
447
  }
402
448
 
@@ -408,6 +454,7 @@ var STANDALONE_COLOR_IDS = [
408
454
  '_sd_ctrl-bg-color','_sd_ctrl-link-color',
409
455
  '_sd_ctrl-bq-border-color',
410
456
  '_sd_ctrl-chart-accent',
457
+ '_sd_ctrl-table-border','_sd_ctrl-table-header-bg','_sd_ctrl-table-even-bg','_sd_ctrl-table-odd-bg','_sd_ctrl-table-text',
411
458
  ];
412
459
 
413
460
  var CASCADE_COLOR_IDS = Object.keys(COLOR_VAR_MAP);
@@ -455,6 +502,13 @@ function parseDarkBlock(block) {
455
502
  if (block.chart.background) colors['_sd_ctrl-chart-bg'] = block.chart.background;
456
503
  if (block.chart.textColor) colors['_sd_ctrl-chart-text'] = block.chart.textColor;
457
504
  }
505
+ if (block.table) {
506
+ if (block.table.border) colors['_sd_ctrl-table-border'] = block.table.border;
507
+ if (block.table.headerBackground) colors['_sd_ctrl-table-header-bg'] = block.table.headerBackground;
508
+ if (block.table.evenBackground) colors['_sd_ctrl-table-even-bg'] = block.table.evenBackground;
509
+ if (block.table.oddBackground) colors['_sd_ctrl-table-odd-bg'] = block.table.oddBackground;
510
+ if (block.table.color) colors['_sd_ctrl-table-text'] = block.table.color;
511
+ }
458
512
 
459
513
  return colors;
460
514
  }
@@ -480,9 +534,9 @@ var STYLE_DEFAULTS = {
480
534
  h3: { fontSize: 1.2, fontWeight: 600 },
481
535
  h4: { fontSize: 1.0, fontWeight: 600 },
482
536
  p: { lineHeight: 1.75, marginBottom: 1.1 },
483
- link: { decoration: 'underline' },
537
+ link: { color: '#2563eb', decoration: 'underline' },
484
538
  code: { font: 'JetBrains Mono' },
485
- blockquote: { borderWidth: 3, fontSize: 1.0 },
539
+ blockquote: { borderColor: '#2563eb', borderWidth: 3, fontSize: 1.0 },
486
540
  list: { spacing: 0.3, indent: 1.6 },
487
541
  };
488
542
 
@@ -532,6 +586,78 @@ function stripStyleDefaults(styles) {
532
586
  return result;
533
587
  }
534
588
 
589
+ // ═══════════════════════════════════════════════════════
590
+ // STYLE REFERENCE RESOLVER ($path.to.prop → var(--md-*))
591
+ // ═══════════════════════════════════════════════════════
592
+
593
+ // Maps YAML-schema paths to the CSS custom property that carries their
594
+ // live value. Used by the slide shape DSL so an agent can write
595
+ // `fill=$h1.color` and have the slide pick up whatever the doc's h1
596
+ // color currently is, including dark-mode inversion. The value is
597
+ // resolved at CSS-paint time, not parse time — theme changes Just Work.
598
+ //
599
+ // Keep this list aligned with COLOR_VAR_MAP + CTRL_CSS_MAP above, plus
600
+ // a few convenience aliases (e.g. `headers.color` as well as `h.color`).
601
+ var STYLE_PATH_TO_VAR = {
602
+ // General
603
+ 'background': '--md-bg',
604
+ 'color': '--md-color',
605
+ 'fontFamily': '--md-font-family',
606
+
607
+ // Headings
608
+ 'h.color': '--md-h-color',
609
+ 'headers.color': '--md-h-color',
610
+ 'headers.fontFamily': '--md-h-font-family',
611
+ 'h1.color': '--md-h1-color',
612
+ 'h2.color': '--md-h2-color',
613
+ 'h3.color': '--md-h3-color',
614
+ 'h4.color': '--md-h4-color',
615
+
616
+ // Paragraph / list
617
+ 'p.color': '--md-p-color',
618
+ 'list.color': '--md-list-color',
619
+
620
+ // Link
621
+ 'link.color': '--md-link-color',
622
+
623
+ // Blocks cascade (code / blockquote / chart parent)
624
+ 'blocks.background': '--md-block-bg',
625
+ 'blocks.color': '--md-block-text',
626
+
627
+ // Code
628
+ 'code.background': '--md-code-bg',
629
+ 'code.color': '--md-code-color',
630
+ 'code.font': '--md-code-font',
631
+
632
+ // Blockquote
633
+ 'blockquote.background': '--md-bq-bg',
634
+ 'blockquote.color': '--md-bq-color',
635
+ 'blockquote.borderColor':'--md-bq-border-color',
636
+
637
+ // Chart
638
+ 'chart.accent': '--md-chart-accent',
639
+ 'chart.background': '--md-chart-bg',
640
+ 'chart.textColor': '--md-chart-text',
641
+
642
+ // Table
643
+ 'table.border': '--md-table-border',
644
+ 'table.headerBackground': '--md-table-header-bg',
645
+ 'table.evenBackground': '--md-table-even-bg',
646
+ 'table.oddBackground': '--md-table-odd-bg',
647
+ 'table.color': '--md-table-text',
648
+ };
649
+
650
+ // Given a raw token (typically a shape attribute value), return the
651
+ // CSS var() expression to use in its place, or null if not a ref.
652
+ // Returns { value, error } so callers can surface unknown refs.
653
+ function resolveStyleRef(token) {
654
+ if (typeof token !== 'string' || token.charAt(0) !== '$') return null;
655
+ var key = token.slice(1);
656
+ var cssVar = STYLE_PATH_TO_VAR[key];
657
+ if (cssVar) return { value: 'var(' + cssVar + ')' };
658
+ return { error: 'unknown style reference "$' + key + '"' };
659
+ }
660
+
535
661
  // ═══════════════════════════════════════════════════════
536
662
  // EXPORTS
537
663
  // ═══════════════════════════════════════════════════════
@@ -549,11 +675,14 @@ exports.hslToHex = hslToHex;
549
675
  exports.controlToCssVars = controlToCssVars;
550
676
  exports.cascadeColor = cascadeColor;
551
677
  exports.invertLightness = invertLightness;
678
+ exports.colorControlRole = colorControlRole;
552
679
  exports.collectStyles = collectStyles;
553
680
  exports.parseDarkBlock = parseDarkBlock;
554
681
  exports.stylesToControls = stylesToControls;
555
682
  exports.STYLE_DEFAULTS = STYLE_DEFAULTS;
556
683
  exports.stripStyleDefaults = stripStyleDefaults;
684
+ exports.STYLE_PATH_TO_VAR = STYLE_PATH_TO_VAR;
685
+ exports.resolveStyleRef = resolveStyleRef;
557
686
 
558
687
  // UMD tail: in Node (tests) this writes to module.exports; in the browser
559
688
  // it creates window.SDocStyles. We use this pattern instead of ES modules
package/README.md DELETED
@@ -1,149 +0,0 @@
1
- # SDocs
2
-
3
- **Read, style, and share markdown files — privately.**
4
-
5
- SDocs is a lightweight, stateless markdown editor with live styling. Your entire document lives in the URL hash — nothing is ever sent to a server.
6
-
7
- **[sdocs.dev](https://sdocs.dev)** &nbsp;|&nbsp; **[npm](https://www.npmjs.com/package/sdocs-dev)** &nbsp;|&nbsp; **[MIT License](LICENSE)**
8
-
9
- ---
10
-
11
- ## What it does
12
-
13
- - **Read** — open any `.md` file with clean, styled formatting
14
- - **Style** — customize fonts, colors, sizes, and spacing via a visual panel or YAML front matter
15
- - **Share** — compress a document into a URL and share it with anyone (no server, no account)
16
- - **Export** — PDF, Word (.docx), raw `.md`, or styled `.md` with front matter
17
-
18
- Everything runs client-side. The server is a small Node.js script that serves static files. The hosted instance at sdocs.dev counts anonymous visits (no IPs, no tracking IDs) which you can see at [sdocs.dev/analytics](https://sdocs.dev/analytics); self-hosted and CLI use never phone home.
19
-
20
- ## CLI
21
-
22
- ```bash
23
- npm i -g sdocs-dev
24
- ```
25
-
26
- ```bash
27
- sdoc README.md # open styled in browser
28
- sdoc share report.md # copy shareable link to clipboard
29
- sdoc share report.md --dark # link opens in dark theme
30
- sdoc share report.md --section "Results" # deep-link to a heading
31
- sdoc new # blank document in write mode
32
- sdoc schema # print all style properties
33
- cat notes.md | sdoc # pipe markdown to browser
34
- ```
35
-
36
- ## How it works
37
-
38
- Documents are compressed with [Brotli](https://en.wikipedia.org/wiki/Brotli) and encoded as [base64url](https://en.wikipedia.org/wiki/Base64#URL_applications) in the URL hash fragment. The hash fragment is never sent to the server by the browser — it stays entirely client-side.
39
-
40
- ```
41
- https://sdocs.dev/#md={brotli compressed + base64url encoded .md}
42
- ```
43
-
44
- Styles are stored as [YAML front matter](https://jekyllrb.com/docs/front-matter/) in the `.md` file using a `styles:` key. Default style values are omitted from URLs to keep them short. Run `sdoc schema` to see all available properties.
45
-
46
- ## Stack
47
-
48
- - **Server**: `server.js` — pure Node.js `http` module
49
- - **Frontend**: plain HTML, CSS, and JS in `public/` — no build step, no framework
50
- - **Markdown parsing**: [marked](https://github.com/markedjs/marked) (the only runtime dependency)
51
- - **Compression**: [brotli-wasm](https://github.com/nicolo-ribaudo/brotli-wasm) (WebAssembly, loaded in browser)
52
- - **Tests**: `node test/run.js` — custom red/green harness using Node `assert`
53
- - **Browser tests**: [Playwright](https://playwright.dev/) (Chromium)
54
-
55
- ## Contributing
56
-
57
- ### Setup
58
-
59
- ```bash
60
- git clone https://github.com/espressoplease/SDocs.git
61
- cd SDocs
62
- npm install
63
- ```
64
-
65
- ### Run locally
66
-
67
- ```bash
68
- node server.js # http://localhost:3000
69
- PORT=8080 node server.js # custom port
70
- SDOCS_DEV=1 node server.js # dev mode: no-cache headers, service worker disabled
71
- ```
72
-
73
- `SDOCS_DEV=1` (or `NODE_ENV=development`) disables browser caching for CSS/JS and tells the app to unregister the service worker and clear its caches on load, so edits to frontend code are picked up instantly without hard-refreshing. Leave it unset in production.
74
-
75
- ### Run tests
76
-
77
- ```bash
78
- node test/run.js # unit + integration tests
79
- npx playwright test test/write-mode.spec.js # browser tests (needs Chromium)
80
- ```
81
-
82
- ### Point the CLI at your local server
83
-
84
- By default the CLI opens URLs on `https://sdocs.dev`. When developing, point it at your local instance:
85
-
86
- ```bash
87
- # Per-command
88
- sdoc README.md --url http://localhost:3000
89
- sdoc share README.md --url http://localhost:3000
90
-
91
- # Or set once for your session
92
- export SDOCS_URL=http://localhost:3000
93
- sdoc README.md
94
- sdoc share README.md
95
- ```
96
-
97
- ### Project structure
98
-
99
- ```
100
- server.js # Node.js static file server
101
- bin/sdocs-dev.js # CLI entry point
102
- public/
103
- index.html # Single HTML file (markup only)
104
- css/ # Modular CSS (tokens, layout, rendered, panel, mobile)
105
- sdocs-yaml.js # YAML front matter parser (UMD, shared with Node)
106
- sdocs-styles.js # Style data tables + logic (UMD, shared with tests)
107
- sdocs-state.js # Shared mutable state namespace (window.SDocs)
108
- sdocs-theme.js # Fonts, dark mode, theme toggle
109
- sdocs-controls.js # CSS variable management, color cascade
110
- sdocs-export.js # PDF/Word/MD export
111
- sdocs-write.js # Write mode (contentEditable)
112
- sdocs-app.js # Core app: render, sync, modes, compression, init
113
- brotli-wasm-v1.js # Brotli WASM wrapper (IIFE)
114
- brotli_wasm_bg.wasm # Brotli WebAssembly binary
115
- vendor/marked.min.js # Markdown parser
116
- sw.js # Service worker (stale-while-revalidate + version check)
117
- sdoc.md # Landing page content
118
- test/
119
- run.js # Test runner entry point
120
- runner.js # Shared test harness
121
- test-yaml.js # YAML parser tests
122
- test-styles.js # Style system tests (including stripStyleDefaults)
123
- test-cli.js # CLI argument parsing + URL building tests
124
- test-slugify.js # Slugify + heading dedup tests
125
- test-base64.js # Base64 UTF-8 roundtrip tests
126
- test-files.js # File existence + content assertions
127
- test-http.js # HTTP server tests
128
- write-mode.spec.js # Playwright write mode tests
129
- sample.smd # Test fixture (styled markdown)
130
- bench-compression.js # Compression benchmark (deflate vs brotli)
131
- ```
132
-
133
- ### Architecture notes
134
-
135
- The app is entirely stateless. All state lives in `window.SDocs` in the browser. There is no build step — all JS loads as plain `<script>` tags. Modules that need to run in both the browser and Node (for tests) use a UMD IIFE pattern.
136
-
137
- Styles are driven by CSS custom properties on `#rendered`. Every control in the style panel maps to a `--md-*` CSS variable. When exporting, `collectStyles()` reads the current control values from the DOM.
138
-
139
- ### Guidelines
140
-
141
- - No build step. All browser JS must work as plain `<script>` tags.
142
- - One runtime dependency (`marked`). Add dependencies only when there's no reasonable alternative.
143
- - Keep the server simple. It serves static files — no API routes, no database.
144
- - Test everything that's testable without a browser in `test/run.js`. Use Playwright for browser-specific behavior.
145
- - Style properties that match defaults should be omitted (see `stripStyleDefaults` in `sdocs-styles.js`).
146
-
147
- ## License
148
-
149
- [MIT](LICENSE)
File without changes
File without changes