@chromamark/renderer 0.1.0 → 0.2.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/README.md +28 -2
- package/dist/chromamark.esm.js +79 -25
- package/dist/chromamark.min.js +10 -9
- package/package.json +1 -1
- package/src/browser-core.js +47 -3
- package/src/critic.js +15 -1
- package/src/inline.js +54 -15
package/README.md
CHANGED
|
@@ -63,13 +63,32 @@ ChromaMark.autoRender();
|
|
|
63
63
|
| `autoRender(options?)` | Inject the theme, then render every target on the page. |
|
|
64
64
|
| `renderElement(target, opts?)` | Render one element (selector or node) in place / as a sibling. |
|
|
65
65
|
| `renderAll(selector?, opts?)` | Render all matching elements. |
|
|
66
|
+
| `renderSrc(target, opts?)` | Fetch the element's `data-chromamark-src` file and render it in place. |
|
|
66
67
|
| `injectTheme(doc?)` | Add the theme `<style>` once (idempotent). |
|
|
67
68
|
| `render(src, opts?)` | ChromaMark string → HTML. |
|
|
68
69
|
| `theme` | The theme CSS as a string. |
|
|
69
70
|
|
|
70
71
|
Targets default to `<script type="text/chromamark">`, `template.chromamark`,
|
|
71
|
-
`.chromamark`,
|
|
72
|
-
loading `<script>` runs `autoRender()`
|
|
72
|
+
`.chromamark`, `[data-chromamark]`, and `[data-chromamark-src]`. Adding
|
|
73
|
+
`data-chromamark-auto` to the loading `<script>` runs `autoRender()`
|
|
74
|
+
automatically once the DOM is ready.
|
|
75
|
+
|
|
76
|
+
### Load ChromaMark from an external file
|
|
77
|
+
|
|
78
|
+
Point an element at a `.cm` file with `data-chromamark-src` and the bundle
|
|
79
|
+
fetches and renders it — the same idea as an external script or stylesheet, so
|
|
80
|
+
the page itself stays lean:
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<div data-chromamark-src="report.cm"></div>
|
|
84
|
+
<script src="https://cdn.jsdelivr.net/npm/@chromamark/renderer/dist/chromamark.min.js"
|
|
85
|
+
data-chromamark-auto></script>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The file is fetched with `fetch()`, so the page must be served over `http(s)`
|
|
89
|
+
(not opened as `file://`) and same-origin/CORS rules apply. If the file can't be
|
|
90
|
+
loaded the element keeps its existing content and gets a `data-chromamark-error`
|
|
91
|
+
attribute, so a missing file degrades gracefully instead of throwing.
|
|
73
92
|
|
|
74
93
|
## Theme
|
|
75
94
|
|
|
@@ -96,6 +115,13 @@ npm test # node:test suite
|
|
|
96
115
|
npm run build # esbuild → dist/chromamark.esm.js, dist/chromamark.min.js, dist/chromamark.css
|
|
97
116
|
```
|
|
98
117
|
|
|
118
|
+
## Credits
|
|
119
|
+
|
|
120
|
+
Inline change-tracking syntax is adopted from **[CriticMarkup](http://criticmarkup.com/)**
|
|
121
|
+
(© 2013 Gabe Weatherhead & Erik Hess, Apache-2.0); ChromaMark's parser is an original,
|
|
122
|
+
independent implementation. See the
|
|
123
|
+
[main README](https://github.com/cjfravel-dev/ChromaMark#prior-art--credits) for full credits.
|
|
124
|
+
|
|
99
125
|
## License
|
|
100
126
|
|
|
101
127
|
Modified MIT License with a SaaS source-availability provision — see [LICENSE.md](./LICENSE.md).
|
package/dist/chromamark.esm.js
CHANGED
|
@@ -2387,12 +2387,12 @@ function escapedSplit(str) {
|
|
|
2387
2387
|
const max = str.length;
|
|
2388
2388
|
let pos = 0;
|
|
2389
2389
|
let ch = str.charCodeAt(pos);
|
|
2390
|
-
let
|
|
2390
|
+
let isEscaped2 = false;
|
|
2391
2391
|
let lastPos = 0;
|
|
2392
2392
|
let current = "";
|
|
2393
2393
|
while (pos < max) {
|
|
2394
2394
|
if (ch === 124) {
|
|
2395
|
-
if (!
|
|
2395
|
+
if (!isEscaped2) {
|
|
2396
2396
|
result.push(current + str.substring(lastPos, pos));
|
|
2397
2397
|
current = "";
|
|
2398
2398
|
lastPos = pos + 1;
|
|
@@ -2401,7 +2401,7 @@ function escapedSplit(str) {
|
|
|
2401
2401
|
lastPos = pos;
|
|
2402
2402
|
}
|
|
2403
2403
|
}
|
|
2404
|
-
|
|
2404
|
+
isEscaped2 = ch === 92;
|
|
2405
2405
|
pos++;
|
|
2406
2406
|
ch = str.charCodeAt(pos);
|
|
2407
2407
|
}
|
|
@@ -5673,10 +5673,14 @@ var SIGILS = { "!": "pill", ".": "text", "=": "meter" };
|
|
|
5673
5673
|
function unescape(text2) {
|
|
5674
5674
|
return text2.replace(/\\([\s\S])/g, "$1");
|
|
5675
5675
|
}
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5676
|
+
var WS = /\s/;
|
|
5677
|
+
function splitInline(src, from, end) {
|
|
5678
|
+
let s = from;
|
|
5679
|
+
while (s < end && WS.test(src[s])) s++;
|
|
5680
|
+
let e = s;
|
|
5681
|
+
while (e < end && !WS.test(src[e])) e++;
|
|
5682
|
+
if (e === s) return null;
|
|
5683
|
+
return { specToken: src.slice(s, e), restStart: e };
|
|
5680
5684
|
}
|
|
5681
5685
|
function meterWidth(value) {
|
|
5682
5686
|
let pct;
|
|
@@ -5694,18 +5698,32 @@ function meterWidth(value) {
|
|
|
5694
5698
|
pct = Math.max(0, Math.min(100, pct));
|
|
5695
5699
|
return String(+pct.toFixed(2));
|
|
5696
5700
|
}
|
|
5697
|
-
function
|
|
5698
|
-
const
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5701
|
+
function nextIndex(state, slot, needle, from) {
|
|
5702
|
+
const c = state[slot];
|
|
5703
|
+
if (c !== void 0 && c.from <= from && (c.at === -1 || from <= c.at)) return c.at;
|
|
5704
|
+
const at = state.src.indexOf(needle, from);
|
|
5705
|
+
state[slot] = { from, at };
|
|
5706
|
+
return at;
|
|
5707
|
+
}
|
|
5708
|
+
function isEscaped(src, pos, floor2) {
|
|
5709
|
+
let n = 0;
|
|
5710
|
+
for (let i = pos - 1; i >= floor2 && src.charCodeAt(i) === 92; i--) n++;
|
|
5711
|
+
return (n & 1) === 1;
|
|
5712
|
+
}
|
|
5713
|
+
function nextUnescaped(state, slot, needle, from, floor2, max) {
|
|
5714
|
+
let at = nextIndex(state, slot, needle, from);
|
|
5715
|
+
while (at !== -1 && at < max && isEscaped(state.src, at, floor2)) {
|
|
5716
|
+
at = nextIndex(state, slot, needle, at + 1);
|
|
5707
5717
|
}
|
|
5708
|
-
return
|
|
5718
|
+
return at;
|
|
5719
|
+
}
|
|
5720
|
+
function findClose(state, from) {
|
|
5721
|
+
const max = state.posMax;
|
|
5722
|
+
const bracket = nextUnescaped(state, "_cmBr", "]", from, from, max);
|
|
5723
|
+
if (bracket === -1 || bracket >= max) return -1;
|
|
5724
|
+
const newline2 = nextUnescaped(state, "_cmNl", "\n", from, from, max);
|
|
5725
|
+
if (newline2 !== -1 && newline2 < bracket) return -1;
|
|
5726
|
+
return bracket;
|
|
5709
5727
|
}
|
|
5710
5728
|
function makeRule(enabled) {
|
|
5711
5729
|
return function chromaInline(state, silent) {
|
|
@@ -5715,12 +5733,12 @@ function makeRule(enabled) {
|
|
|
5715
5733
|
if (!kind || !enabled[kind]) return false;
|
|
5716
5734
|
const end = findClose(state, start + 2);
|
|
5717
5735
|
if (end === -1) return false;
|
|
5718
|
-
const
|
|
5719
|
-
const parts =
|
|
5736
|
+
const src = state.src;
|
|
5737
|
+
const parts = splitInline(src, start + 2, end);
|
|
5720
5738
|
if (!parts) return false;
|
|
5721
5739
|
const spec = parseSpec(parts.specToken);
|
|
5722
5740
|
if (!spec) return false;
|
|
5723
|
-
const rest = unescape(parts.
|
|
5741
|
+
const rest = unescape(src.slice(parts.restStart, end).trim());
|
|
5724
5742
|
let token;
|
|
5725
5743
|
if (kind === "meter") {
|
|
5726
5744
|
if (!rest) return false;
|
|
@@ -5780,6 +5798,14 @@ var KINDS = {
|
|
|
5780
5798
|
"==": { kind: "mark", close: "==}" },
|
|
5781
5799
|
">>": { kind: "comment", close: "<<}" }
|
|
5782
5800
|
};
|
|
5801
|
+
function findClose2(state, needle, from) {
|
|
5802
|
+
const slot = "_cmCritic" + needle;
|
|
5803
|
+
const c = state[slot];
|
|
5804
|
+
if (c !== void 0 && c.from <= from && (c.at === -1 || from <= c.at)) return c.at;
|
|
5805
|
+
const at = state.src.indexOf(needle, from);
|
|
5806
|
+
state[slot] = { from, at };
|
|
5807
|
+
return at;
|
|
5808
|
+
}
|
|
5783
5809
|
function criticRule(state, silent) {
|
|
5784
5810
|
const start = state.pos;
|
|
5785
5811
|
const src = state.src;
|
|
@@ -5787,7 +5813,7 @@ function criticRule(state, silent) {
|
|
|
5787
5813
|
const spec = KINDS[src.slice(start + 1, start + 3)];
|
|
5788
5814
|
if (!spec) return false;
|
|
5789
5815
|
const contentStart = start + 3;
|
|
5790
|
-
const closeIdx =
|
|
5816
|
+
const closeIdx = findClose2(state, spec.close, contentStart);
|
|
5791
5817
|
if (closeIdx === -1 || closeIdx + spec.close.length > state.posMax) return false;
|
|
5792
5818
|
const raw = src.slice(contentStart, closeIdx);
|
|
5793
5819
|
if (!silent) {
|
|
@@ -6033,7 +6059,9 @@ function render(src, options = {}) {
|
|
|
6033
6059
|
// src/browser-core.js
|
|
6034
6060
|
var STYLE_ID = "chromamark-theme";
|
|
6035
6061
|
var DONE_ATTR = "data-chromamark-done";
|
|
6036
|
-
var
|
|
6062
|
+
var SRC_ATTR = "data-chromamark-src";
|
|
6063
|
+
var ERR_ATTR = "data-chromamark-error";
|
|
6064
|
+
var DEFAULT_SELECTOR = 'script[type="text/chromamark"], template.chromamark, [data-chromamark], [data-chromamark-src], .chromamark';
|
|
6037
6065
|
var theme = "";
|
|
6038
6066
|
function configureTheme(css) {
|
|
6039
6067
|
theme = css || "";
|
|
@@ -6071,20 +6099,44 @@ function sourceOf(el, tag) {
|
|
|
6071
6099
|
function renderElement(target, options) {
|
|
6072
6100
|
const el = resolve(target);
|
|
6073
6101
|
if (!el || el.hasAttribute(DONE_ATTR)) return null;
|
|
6102
|
+
if (el.hasAttribute && el.hasAttribute(SRC_ATTR)) return renderSrc(el, options);
|
|
6103
|
+
const doc = el.ownerDocument || (typeof document !== "undefined" ? document : null);
|
|
6074
6104
|
const tag = (el.tagName || "").toLowerCase();
|
|
6075
6105
|
const html = render(dedent(sourceOf(el, tag)), options);
|
|
6076
6106
|
el.setAttribute(DONE_ATTR, "");
|
|
6077
6107
|
if (tag === "script" || tag === "template") {
|
|
6078
|
-
const out =
|
|
6108
|
+
const out = doc.createElement("div");
|
|
6079
6109
|
out.className = "chromamark-output";
|
|
6080
6110
|
out.innerHTML = html;
|
|
6081
|
-
el.parentNode.insertBefore(out, el.nextSibling);
|
|
6111
|
+
if (el.parentNode) el.parentNode.insertBefore(out, el.nextSibling);
|
|
6082
6112
|
return out;
|
|
6083
6113
|
}
|
|
6084
6114
|
el.innerHTML = html;
|
|
6085
6115
|
el.classList.add("chromamark-output");
|
|
6086
6116
|
return el;
|
|
6087
6117
|
}
|
|
6118
|
+
function renderSrc(target, options) {
|
|
6119
|
+
const el = resolve(target);
|
|
6120
|
+
if (!el || el.hasAttribute(DONE_ATTR)) return Promise.resolve(null);
|
|
6121
|
+
const url = el.getAttribute(SRC_ATTR);
|
|
6122
|
+
if (!url) return Promise.resolve(null);
|
|
6123
|
+
el.setAttribute(DONE_ATTR, "");
|
|
6124
|
+
const view = el.ownerDocument && el.ownerDocument.defaultView;
|
|
6125
|
+
const doFetch = view && view.fetch || (typeof fetch !== "undefined" ? fetch : null);
|
|
6126
|
+
const fail = (message) => {
|
|
6127
|
+
el.setAttribute(ERR_ATTR, message);
|
|
6128
|
+
return null;
|
|
6129
|
+
};
|
|
6130
|
+
if (!doFetch) return Promise.resolve(fail("ChromaMark: fetch is unavailable"));
|
|
6131
|
+
return Promise.resolve().then(() => doFetch(url)).then((res) => {
|
|
6132
|
+
if (!res || !res.ok) throw new Error(`HTTP ${res ? res.status : "?"}`);
|
|
6133
|
+
return res.text();
|
|
6134
|
+
}).then((text2) => {
|
|
6135
|
+
el.innerHTML = render(text2, options);
|
|
6136
|
+
el.classList.add("chromamark-output");
|
|
6137
|
+
return el;
|
|
6138
|
+
}).catch((err) => fail(`ChromaMark: failed to load ${url} (${err && err.message || err})`));
|
|
6139
|
+
}
|
|
6088
6140
|
function renderAll(selector, options) {
|
|
6089
6141
|
const nodes = document.querySelectorAll(selector || DEFAULT_SELECTOR);
|
|
6090
6142
|
return Array.from(nodes).map((el) => renderElement(el, options));
|
|
@@ -6097,6 +6149,7 @@ var ChromaMark = {
|
|
|
6097
6149
|
render: render2,
|
|
6098
6150
|
renderElement,
|
|
6099
6151
|
renderAll,
|
|
6152
|
+
renderSrc,
|
|
6100
6153
|
injectTheme,
|
|
6101
6154
|
autoRender,
|
|
6102
6155
|
createRenderer,
|
|
@@ -6128,5 +6181,6 @@ export {
|
|
|
6128
6181
|
render2 as render,
|
|
6129
6182
|
renderAll,
|
|
6130
6183
|
renderElement,
|
|
6184
|
+
renderSrc,
|
|
6131
6185
|
theme
|
|
6132
6186
|
};
|