rapier-markdown-kit 1.2.7 → 1.2.9
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 +32 -30
- package/dist/agent/will.mjs +1 -6
- package/dist/images/gif.mjs +0 -3
- package/dist/images/playback.mjs +1 -10
- package/dist/kit/assets.mjs +0 -1
- package/dist/kit/conformance/measurer.mjs +0 -0
- package/dist/kit/conformance/run.mjs +1 -8
- package/dist/kit/export-fonts.mjs +0 -14
- package/dist/kit/index.mjs +1 -1
- package/dist/kit/ledger/authored-origin.mjs +0 -5
- package/dist/kit/ledger/authorship.mjs +0 -2
- package/dist/kit/ledger/carried.d.mts +20 -0
- package/dist/kit/ledger/carried.mjs +4 -7
- package/dist/kit/ledger/data.mjs +0 -8
- package/dist/kit/ledger/format.mjs +0 -25
- package/dist/kit/ledger/hash.mjs +11 -7
- package/dist/kit/ledger/history.mjs +10 -29
- package/dist/kit/ledger/inverse-source.mjs +0 -46
- package/dist/kit/ledger/journal-records.mjs +24 -56
- package/dist/kit/ledger/merge.mjs +0 -27
- package/dist/kit/ledger/text-merge.mjs +0 -20
- package/dist/kit/ledger/transport.mjs +0 -47
- package/dist/kit/ledger.d.mts +88 -0
- package/dist/kit/ledger.mjs +14 -0
- package/dist/kit/markdown-it.d.mts +4 -0
- package/dist/kit/markdown-it.mjs +36 -0
- package/dist/kit/pdf-glyphs.mjs +39 -45
- package/dist/kit/pdf-source.d.mts +5 -0
- package/dist/kit/pdf-source.mjs +125 -0
- package/dist/kit/pdf-vectors.mjs +0 -3
- package/dist/kit/print-capitals.mjs +0 -4
- package/dist/kit/render-entry.mjs +0 -1
- package/dist/kit/render-ink.mjs +0 -5
- package/dist/kit/render-markdown.mjs +15 -119
- package/dist/kit/render-print.mjs +4 -49
- package/dist/kit/render-sanitize.mjs +22 -58
- package/dist/kit/render-styles.mjs +0 -7
- package/dist/kit/render-work.mjs +0 -12
- package/dist/kit/render.mjs +18 -173
- package/dist/kit/will.d.mts +42 -0
- package/dist/layout/hyphenation.mjs +0 -23
- package/dist/layout/line-plan.mjs +0 -20
- package/dist/layout/model.mjs +0 -55
- package/dist/layout/paragraph-fit.mjs +50 -60
- package/dist/layout/preparation.mjs +23 -56
- package/dist/layout/prepared-paragraph.mjs +0 -25
- package/dist/layout/typography.mjs +103 -41
- package/dist/packages/rapier-diff/concerns.mjs +0 -16
- package/dist/packages/rapier-diff/diff.mjs +0 -7
- package/dist/packages/rapier-diff/index.mjs +0 -1
- package/dist/packages/rapier-diff/matching.mjs +0 -19
- package/dist/packages/rapier-diff/merge.mjs +0 -9
- package/dist/packages/rapier-diff/moves.mjs +0 -7
- package/dist/packages/rapier-diff/structure.mjs +0 -2
- package/dist/packages/rapier-diff/tokens.mjs +0 -6
- package/dist/packages/rapier-diff/walk.mjs +0 -12
- package/dist/spec/document-fonts.mjs +0 -9
- package/dist/spec/document-settings.mjs +6 -25
- package/dist/spec/frontmatter.mjs +0 -1
- package/dist/spec/html-blocks.mjs +3 -37
- package/dist/spec/md-assets.mjs +2 -61
- package/dist/spec/md-layout.mjs +0 -34
- package/dist/spec/md-marks.mjs +0 -51
- package/dist/spec/md-source.mjs +0 -16
- package/dist/spec/word-sidecar.mjs +0 -2
- package/package.json +21 -4
- package/style.css +38 -21
package/dist/spec/md-layout.mjs
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
|
|
3
|
-
// `rotate`: degrees in (-180, 180], one decimal, omitted when 0; never written for a Rapier drawing (its turn lives in its SVG).
|
|
4
|
-
// `opacity`: a picture's fade, a whole percent from 5 to 100, omitted at 100; the picture's bytes never change.
|
|
5
|
-
// `lines`: a picture or paragraph's initial letter spans 1 to 12 lines, from the first line's cap height to the Nth
|
|
6
|
-
// baseline. A picture keeps its aspect ratio; never with `width`.
|
|
7
|
-
// `first`/`indent`: signed levels of 2em, at most six decimals, in [-64,64], omitted at zero. `marker` moves a list
|
|
8
|
-
// item's marker independently of its words, on the same scale; only the item's leading paragraph carries it.
|
|
9
|
-
// `lock=on`: a picture locked in place: a tap goes to the words, never the picture; a hold takes it to unlock it. Omitted when off.
|
|
10
|
-
// `hyphens=auto`, `caps=small`, `hanging=on`: paragraph typography; absence preserves ordinary text.
|
|
11
3
|
export const fields = new Set(['align', 'width', 'lines', 'wrap', 'x', 'y', 'rotate', 'opacity', 'first', 'indent', 'marker', 'lock', 'hyphens', 'caps', 'hanging']);
|
|
12
4
|
export const alignments = new Set(['left', 'center', 'right', 'justify']);
|
|
13
|
-
// `behind`/`front`: out of flow; the paragraph lays out as though the picture were absent.
|
|
14
5
|
export const wraps = new Set(['around', 'box', 'behind', 'front']);
|
|
15
6
|
const has = (value, key) => Object.hasOwn(value, key);
|
|
16
7
|
const percent = value => typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 100;
|
|
@@ -18,7 +9,6 @@ const lineCount = value => Number.isInteger(value) && value >= 1 && value <= 12;
|
|
|
18
9
|
export const INDENT_LIMIT = 64;
|
|
19
10
|
export const indentLevel = value => typeof value === 'number' && Number.isFinite(value) && value !== 0 &&
|
|
20
11
|
Math.abs(value) <= INDENT_LIMIT && Number(value.toFixed(6)) === value;
|
|
21
|
-
// A key press advances an imported measure by one level without truncating its fraction or jumping across zero.
|
|
22
12
|
export function stepIndent(value, step) {
|
|
23
13
|
const next = Number(((value || 0) + step).toFixed(6));
|
|
24
14
|
if (Math.abs(next) > INDENT_LIMIT) return value || 0;
|
|
@@ -27,7 +17,6 @@ export function stepIndent(value, step) {
|
|
|
27
17
|
const oneDecimalDegrees = value => typeof value === 'number' && Number.isFinite(value) &&
|
|
28
18
|
value > -180 && value <= 180 && Math.abs(value * 10 - Math.round(value * 10)) < 1e-9;
|
|
29
19
|
|
|
30
|
-
/** Validate values independently of their Markdown attachment context. */
|
|
31
20
|
export function validLayout(value) {
|
|
32
21
|
if (!value || typeof value !== 'object' || Array.isArray(value) ||
|
|
33
22
|
Object.keys(value).some(key => !fields.has(key))) return false;
|
|
@@ -48,7 +37,6 @@ export function validLayout(value) {
|
|
|
48
37
|
!(has(value, 'align') && (has(value, 'wrap') || has(value, 'x')));
|
|
49
38
|
}
|
|
50
39
|
|
|
51
|
-
/** Parse the closed v1 grammar. Geometry values are returned as numbers. */
|
|
52
40
|
export function parseLayout(comment) {
|
|
53
41
|
if (typeof comment !== 'string' || /[^\t\x20-\x7e]/.test(comment)) return null;
|
|
54
42
|
const match = /^<!--md-layout:v1[ \t]+([^\r\n]*?)[ \t]*-->$/.exec(comment);
|
|
@@ -65,12 +53,10 @@ export function parseLayout(comment) {
|
|
|
65
53
|
number[1] === '100' && /[1-9]/.test(number[2] || '')) return null;
|
|
66
54
|
value[key] = Number(raw.slice(0, -1));
|
|
67
55
|
} else if (key === 'y') {
|
|
68
|
-
// Negative allowed; signed like rotate, never "-0em".
|
|
69
56
|
const number = /^(-)?(0|[1-9]\d*)(?:\.\d+)?em$/.exec(raw);
|
|
70
57
|
if (!number || (number[1] && Number(raw.slice(1, -2)) === 0)) return null;
|
|
71
58
|
value.y = Number(raw.slice(0, -2));
|
|
72
59
|
} else if (key === 'rotate') {
|
|
73
|
-
// No '+', exponent, '°' or "-0deg"; 0 is unsigned.
|
|
74
60
|
const number = /^(-)?(0|[1-9]\d*)(?:\.(\d))?deg$/.exec(raw);
|
|
75
61
|
if (!number) return null;
|
|
76
62
|
const magnitude = Number(number[2] + (number[3] ? '.' + number[3] : ''));
|
|
@@ -90,16 +76,12 @@ export function parseLayout(comment) {
|
|
|
90
76
|
return validLayout(value) ? value : null;
|
|
91
77
|
}
|
|
92
78
|
|
|
93
|
-
// Comment delimiters are unsafe in sanitized attributes. Markdown stays literal;
|
|
94
|
-
// rendered attributes carry encodeURIComponent(marker), including its '%' signs.
|
|
95
79
|
export function decodeLayoutAttribute(value) {
|
|
96
80
|
try { return decodeURIComponent(value || ''); } catch (_) { return ''; }
|
|
97
81
|
}
|
|
98
82
|
|
|
99
83
|
export const parseLayoutAttribute = value => parseLayout(decodeLayoutAttribute(value));
|
|
100
84
|
|
|
101
|
-
// Indentation uses the document's body type, including on headings. Native table measurements
|
|
102
|
-
// become ordinary CSS points; an item's margin is compensated by its paragraph and nested list.
|
|
103
85
|
export function textStyle(value, {portable = false, pointSize, indentOffset = 0} = {}) {
|
|
104
86
|
if (!validLayout(value)) return '';
|
|
105
87
|
const property = {first: 'text-indent', indent: 'margin-inline-start'};
|
|
@@ -115,9 +97,6 @@ export function textStyle(value, {portable = false, pointSize, indentOffset = 0}
|
|
|
115
97
|
return css.join(';');
|
|
116
98
|
}
|
|
117
99
|
|
|
118
|
-
// Raw HTML tables carry ordinary paragraph CSS. Read the browser's parsed declarations on the same
|
|
119
|
-
// admitted scale; the native caller supplies the body's point size. A list marker belongs to its own li,
|
|
120
|
-
// never to a paragraph or a picture.
|
|
121
100
|
export function textLayout(style, {listMarker = false, pointSize, offset = 0} = {}) {
|
|
122
101
|
const value = {};
|
|
123
102
|
for (const [key, property] of (listMarker ? [['marker', 'margin-inline-start']] : [['first', 'text-indent'], ['indent', 'margin-inline-start']])) {
|
|
@@ -125,16 +104,12 @@ export function textLayout(style, {listMarker = false, pointSize, offset = 0} =
|
|
|
125
104
|
const match = /^(-?(?:0|[1-9]\d*)(?:\.\d{1,6})?)(em|pt)$/.exec(raw);
|
|
126
105
|
const level = Number(((match ? Number(match[1]) / (match[2] === 'pt' ? pointSize : 1) / 2 : 0) +
|
|
127
106
|
(key === 'first' ? 0 : offset)).toFixed(6));
|
|
128
|
-
// Public CSS may compensate an enclosing item's margin; the resolved native coordinate,
|
|
129
|
-
// rather than each intermediate declaration, is the measurement the writer must keep.
|
|
130
107
|
if (Number.isFinite(level) && level) value[key] = level;
|
|
131
108
|
}
|
|
132
109
|
if (!listMarker && style?.getPropertyValue('hyphens').trim() === 'auto') value.hyphens = 'auto';
|
|
133
110
|
return value;
|
|
134
111
|
}
|
|
135
112
|
|
|
136
|
-
// The picture's own CSS: its normal-flow width and x, its height in lines, and its fade. `1cap` is the cap height of the
|
|
137
|
-
// picture's own text; a renderer that sets the picture beside its anchor measures that paragraph's line and cap instead.
|
|
138
113
|
export function imageStyle(value) {
|
|
139
114
|
if (!validLayout(value)) return '';
|
|
140
115
|
const parts = [];
|
|
@@ -144,7 +119,6 @@ export function imageStyle(value) {
|
|
|
144
119
|
decimal(Number(Math.max(0, Math.min(100 - width, value.x - width / 2)).toFixed(6))) + '%;margin-right:0';
|
|
145
120
|
parts.push('width:' + decimal(width) + '%;height:auto' + left);
|
|
146
121
|
}
|
|
147
|
-
// Wider than the column at its own aspect, it is contained in the N-line box, never stretched.
|
|
148
122
|
if (value.lines != null) parts.push('width:auto;max-width:100%;height:' + linesHeightCss(value.lines) + ';object-fit:contain');
|
|
149
123
|
if (value.opacity != null && value.opacity < 100) parts.push('opacity:' + decimal(value.opacity / 100));
|
|
150
124
|
return parts.join(';');
|
|
@@ -152,23 +126,17 @@ export function imageStyle(value) {
|
|
|
152
126
|
|
|
153
127
|
export const linesHeightCss = lines => lines === 1 ? '1cap' : 'calc(' + (lines - 1) + ' * var(--md-line, 1.6em) + 1cap)';
|
|
154
128
|
|
|
155
|
-
// An HTML object has an outer clip box and an independent browser viewport.
|
|
156
|
-
// SCALE keeps that authored viewport; REFLOW keeps measured content height.
|
|
157
129
|
export function objectHeight(width, naturalHeight, attrs, line) {
|
|
158
130
|
if (attrs.height && attrs.height !== 'auto') return Math.min(4096, Number(attrs.height) * line);
|
|
159
131
|
const [w, h] = (attrs.viewport || '640x360').split('x').map(Number);
|
|
160
132
|
return attrs.resize === 'scale' ? Math.min(4096, width * h / w) : naturalHeight;
|
|
161
133
|
}
|
|
162
134
|
|
|
163
|
-
// Controls remain real DOM nodes while their surrounding text flows. A checkbox, link or
|
|
164
|
-
// disclosure is not a reason to exclude its entire block from picture placement.
|
|
165
135
|
export function wrapTextBlock(element) {
|
|
166
136
|
return element && /^(P|H[1-6]|UL|OL|BLOCKQUOTE|DETAILS|DL)$/.test(element.tagName) &&
|
|
167
137
|
element.textContent.trim() && !element.querySelector('img,table,pre,figure,math,.math-rendered') ? element : null;
|
|
168
138
|
}
|
|
169
139
|
|
|
170
|
-
// The one wrap-owner rule (layout/browser.js, kit/render.mjs, editor/share.js): text after, else before, skipping pictures,
|
|
171
|
-
// metadata and empty paragraphs; any other block is a barrier in that direction.
|
|
172
140
|
export function wrapNeighbour(node, classify, step = (n, direction) => n && n[direction > 0 ? 'nextElementSibling' : 'previousElementSibling']) {
|
|
173
141
|
for (const direction of [1, -1]) {
|
|
174
142
|
for (let sibling = step(node, direction); sibling; sibling = step(sibling, direction)) {
|
|
@@ -182,7 +150,6 @@ export function wrapNeighbour(node, classify, step = (n, direction) => n && n[di
|
|
|
182
150
|
return null;
|
|
183
151
|
}
|
|
184
152
|
|
|
185
|
-
// Narrower than this, a line breaker splits words letter by letter.
|
|
186
153
|
export function wrapColumnFloor(fontSize) {
|
|
187
154
|
return Math.max(40, 3.5 * (fontSize || 16));
|
|
188
155
|
}
|
|
@@ -199,7 +166,6 @@ function decimal(value) {
|
|
|
199
166
|
return digits.slice(0, point) + '.' + digits.slice(point);
|
|
200
167
|
}
|
|
201
168
|
|
|
202
|
-
/** Write only the supplied facts; no alignment is implied by absence. */
|
|
203
169
|
export function formatLayout(value) {
|
|
204
170
|
if (!validLayout(value)) throw new TypeError('invalid_markdown_layout');
|
|
205
171
|
const pairs = [];
|
package/dist/spec/md-marks.mjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// The one owner of inline typography, ink and page breaks.
|
|
3
2
|
import {markdownSourcePositions} from './md-source.mjs';
|
|
4
3
|
|
|
5
|
-
// The exported page retains this reader alone. The editor uses the same closure, then adds its writers and scans.
|
|
6
4
|
const inkReader = () => {
|
|
7
5
|
// SPDX-License-Identifier: MIT
|
|
8
6
|
const TEXT_COLOR_NAMES = Object.freeze({ green: '#1c7547', red: '#b32034', blue: '#175dc4', gold: '#865818', purple: '#7924d7' });
|
|
@@ -10,16 +8,6 @@ const inkReader = () => {
|
|
|
10
8
|
const COLOR_NAME_ALT = Object.keys(TEXT_COLOR_NAMES).join('|');
|
|
11
9
|
const COLOR_VALUE = '(?:#[0-9a-f]{6}|' + COLOR_NAME_ALT + ')';
|
|
12
10
|
|
|
13
|
-
// Ink (docs/markdown-standard.md, "Ink"): a pen stroke that stays with the words it marks, a paired comment with the color span's shape. The
|
|
14
|
-
// opener holds the stroke:
|
|
15
|
-
// <!--ink KIND [NUMBER] [COLOUR] [w=N] [box=W,H] [at=X,Y] [PATH]-->words<!--/ink-->
|
|
16
|
-
// KIND is the stroke's kind, decided once when it was lifted; COLOUR is exactly the color span's value, red when absent; box is the frame's
|
|
17
|
-
// size when the stroke was drawn and at the frame's offset from the words' box, both in hundredths of an em; w is the pen's width as Draw
|
|
18
|
-
// counts a nib (2 to 24; 9 is the default and is never written): the stroke is drawn 0.11 em wide at 9 and in proportion to w otherwise; PATH
|
|
19
|
-
// is the stroke, its first point absolute in the frame and the rest as moves, hundredths of an em. One spelling: the fields in this order,
|
|
20
|
-
// one space between, integers only. A comment that does not read this way is ordinary comment text and marks nothing. Ink pairs nest:
|
|
21
|
-
// each closer takes the innermost open ink opener. Arrow/end alone take a pairing number, an end takes no other data; under, strike and arrow may omit a path for a line
|
|
22
|
-
// derived from their words.
|
|
23
11
|
const INK_KINDS = Object.freeze(['under', 'strike', 'ring', 'bracket', 'free', 'arrow', 'end']);
|
|
24
12
|
|
|
25
13
|
const INK_PATH_MAX = 160;
|
|
@@ -88,7 +76,6 @@ export const FONT_STACKS = Object.freeze({
|
|
|
88
76
|
hand: Object.freeze({stack: 'hand', css: 'cursive', word: 'Segoe Print'}),
|
|
89
77
|
});
|
|
90
78
|
|
|
91
|
-
// A family is one font-table name, never a CSS list or an HTML fragment.
|
|
92
79
|
export function normalizeFontFamily(value) {
|
|
93
80
|
if (typeof value !== 'string') return null;
|
|
94
81
|
const family = value.trim();
|
|
@@ -106,8 +93,6 @@ export function formatFontRun(family, content) {
|
|
|
106
93
|
return typography.wrap({family}, content);
|
|
107
94
|
}
|
|
108
95
|
|
|
109
|
-
// One typed interpretation for source, DOM and native-file adapters. The worker carries this
|
|
110
|
-
// exact factory and its explicit grammar inputs; no DOM, CSS evaluator or resource loader.
|
|
111
96
|
export function createTypography({normalizeFontFamily, fontFamilyCss, FONT_STACKS}) {
|
|
112
97
|
const LIMIT = 4096, DEPTH = 64;
|
|
113
98
|
const generics = {serif: 'serif', 'sans-serif': 'sans', monospace: 'mono', cursive: 'hand',
|
|
@@ -127,7 +112,6 @@ export function createTypography({normalizeFontFamily, fontFamilyCss, FONT_STACK
|
|
|
127
112
|
function color(value) {
|
|
128
113
|
if (typeof value !== 'string') return null;
|
|
129
114
|
if (/^#[0-9a-f]{6}$/i.test(value.trim())) return value.trim().toLowerCase();
|
|
130
|
-
// CSSOM serializes admitted hex to opaque rgb(). Native HTML import uses this same boundary.
|
|
131
115
|
const rgb = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*1\s*)?\)$/i.exec(value.trim());
|
|
132
116
|
return rgb && rgb.slice(1).every(channel => Number(channel) <= 255)
|
|
133
117
|
? '#' + rgb.slice(1).map(channel => Number(channel).toString(16).padStart(2, '0')).join('') : null;
|
|
@@ -154,7 +138,6 @@ export function createTypography({normalizeFontFamily, fontFamilyCss, FONT_STACK
|
|
|
154
138
|
}
|
|
155
139
|
} else if (!/^[\p{L}_-][\p{L}\p{N}_ -]*$/u.test(css) || /^(?:inherit|initial|unset|revert|revert-layer)$/i.test(css)) return null;
|
|
156
140
|
name = normalizeFontFamily(name);
|
|
157
|
-
// These five names are the existing family-choice aliases, not custom font identities.
|
|
158
141
|
if (name && Object.hasOwn(FONT_STACKS, name.toLowerCase())) return null;
|
|
159
142
|
return name ? Object.freeze({name, css: '"' + name.replace(/[\\"]/g, '\\$&') + '"', custom: true}) : null;
|
|
160
143
|
}
|
|
@@ -178,8 +161,6 @@ export function createTypography({normalizeFontFamily, fontFamilyCss, FONT_STACK
|
|
|
178
161
|
}
|
|
179
162
|
return Object.freeze(result);
|
|
180
163
|
}
|
|
181
|
-
// Rendering drops unsupported declarations; rich editing admits only wholly owned
|
|
182
|
-
// spans so unrelated HTML and rejected attributes keep their exact-source editor.
|
|
183
164
|
function parseStyle(value, editable = false) {
|
|
184
165
|
if (typeof value !== 'string' || value.length > LIMIT) return editable ? null : Object.freeze({});
|
|
185
166
|
const parts = []; let quote = '', start = 0;
|
|
@@ -212,15 +193,11 @@ export function createTypography({normalizeFontFamily, fontFamilyCss, FONT_STACK
|
|
|
212
193
|
const wrap = (value, content) => {
|
|
213
194
|
const start = open(value);
|
|
214
195
|
if (!start || !content) return content;
|
|
215
|
-
// Importers can supply a styled break by itself, or before/after words. Keep
|
|
216
|
-
// those exact line-edge bytes outside the new span so its opener has words.
|
|
217
196
|
const prefix = /^(?:[ \t]*(?:\r\n|\r(?!\n)|\n))+/.exec(content)?.[0] || '';
|
|
218
197
|
let body = content.slice(prefix.length);
|
|
219
198
|
const suffix = /(?:[ \t]*(?:\r\n|\r(?!\n)|\n))+[ \t]*$/.exec(body)?.[0] || '';
|
|
220
199
|
body = body.slice(0, body.length - suffix.length);
|
|
221
200
|
if (!body) return content;
|
|
222
|
-
// Soft/hard line breaks can belong to one Markdown paragraph. A blank line or
|
|
223
|
-
// an opener on a line of its own would instead change the block grammar.
|
|
224
201
|
if (/^\s*\n|\n[ \t]*\n/.test(body.replace(/\r\n?/g, '\n')))
|
|
225
202
|
throw new TypeError('Typography wraps one inline paragraph at a time.');
|
|
226
203
|
return prefix + start + body + '</span>' + suffix;
|
|
@@ -323,8 +300,6 @@ const inkPair = (value, name, nonNegative = false) => {
|
|
|
323
300
|
return pair;
|
|
324
301
|
};
|
|
325
302
|
|
|
326
|
-
// {kind, id?, hex?, width?, box?, at?, path?} to the opener; the path's points are absolute in the frame and become moves after the
|
|
327
|
-
// first. Refuses with a TypeError rather than writing a spelling the reader would not read.
|
|
328
303
|
export function formatInkOpen(mark) {
|
|
329
304
|
if (!mark || typeof mark !== 'object') throw new TypeError('ink: a mark is an object');
|
|
330
305
|
if (!INK_KINDS.includes(mark.kind)) throw new TypeError('ink: the kind is one of ' + INK_KINDS.join(', '));
|
|
@@ -358,7 +333,6 @@ export function formatInkRun(mark, content) {
|
|
|
358
333
|
return formatInkOpen(mark) + content + INK_CLOSE;
|
|
359
334
|
}
|
|
360
335
|
|
|
361
|
-
// Returns {mark, length in UTF-16 units} or null; not end-anchored.
|
|
362
336
|
export function matchInkOpen(text) {
|
|
363
337
|
if (typeof text !== 'string') return null;
|
|
364
338
|
const match = new RegExp('^' + INK_OPEN_PATTERN).exec(text);
|
|
@@ -367,10 +341,6 @@ export function matchInkOpen(text) {
|
|
|
367
341
|
return mark && { mark, length: match[0].length };
|
|
368
342
|
}
|
|
369
343
|
|
|
370
|
-
// The opener's body is what a rendered span carries (a renderer's sanitizer drops an attribute whose value holds the
|
|
371
|
-
// comment's own close): the opener after `<!--ink ` and before `-->`, so the attribute reads `under red box=… 0,95 …`,
|
|
372
|
-
// and only a body that reads as a mark goes back. Read and write through the same grammar, so an invalid body cannot
|
|
373
|
-
// invent a mark.
|
|
374
344
|
export function inkOpenBody(opener) {
|
|
375
345
|
if (typeof opener !== 'string') return null;
|
|
376
346
|
const match = new RegExp('^' + INK_OPEN_PATTERN + '$').exec(opener);
|
|
@@ -384,10 +354,6 @@ export function isInkClose(comment) {
|
|
|
384
354
|
return comment === INK_CLOSE;
|
|
385
355
|
}
|
|
386
356
|
|
|
387
|
-
// Every ink marker in order: {start, end, mark} for an opener (mark null for a closer, and an opener whose path
|
|
388
|
-
// overflows is not a marker at all).
|
|
389
|
-
// Without a prior arrow or adjacent marker bytes, only an edit boundary can empty a span.
|
|
390
|
-
// Read those boundaries through the planned rows before asking the full grammar to retire ink.
|
|
391
357
|
export function mayRetireInk(source, rows) {
|
|
392
358
|
if (source.contains('<!--ink arrow ') || source.contains('<!--ink end ') || source.contains('--><!--')) return true;
|
|
393
359
|
let pieces = [{from: 0, length: source.length}], length = source.length;
|
|
@@ -430,20 +396,14 @@ export function scanInkMarkers(source) {
|
|
|
430
396
|
return markers;
|
|
431
397
|
}
|
|
432
398
|
|
|
433
|
-
// Ink nests so independent strokes can share the same words. A marker that pairs with nothing is text
|
|
434
|
-
// (it shows nothing and marks nothing) and is listed under strays.
|
|
435
399
|
export function pairInkMarkers(source, markers = null) {
|
|
436
400
|
return pairMarkers(source, 'ink', markers);
|
|
437
401
|
}
|
|
438
402
|
|
|
439
|
-
// A document parser may supply its semantic markers, leaving literal code comments untouched.
|
|
440
|
-
// Local inline parsing and source planners need an endpoint's span even when its mate is in another block.
|
|
441
403
|
export function pairInkSpans(source, markers = null) {
|
|
442
404
|
return pairSpanMarkers(source, 'ink', markers);
|
|
443
405
|
}
|
|
444
406
|
|
|
445
|
-
// Color keeps its first opener; ink pairs each closer with the innermost opener. The shared range
|
|
446
|
-
// shape lets custody owners keep every independent stroke, including strokes on identical words.
|
|
447
407
|
export function pairMarkers(source, kind, markers = null) {
|
|
448
408
|
const paired = pairSpanMarkers(source, kind, markers);
|
|
449
409
|
if (kind !== 'ink') return paired;
|
|
@@ -470,9 +430,6 @@ export function pairMarkers(source, kind, markers = null) {
|
|
|
470
430
|
return { runs, strays, arrows };
|
|
471
431
|
}
|
|
472
432
|
|
|
473
|
-
// Where a Will marker may stand (docs/will.md, Conversion). A pair inside a fence, a raw HTML block
|
|
474
|
-
// (until its blank line) or the opening front matter is text: the line is quoted,
|
|
475
|
-
// and a Word or PDF writer that met it as a marker would govern the wrong region. The same frame
|
|
476
433
|
// as spec/frontmatter.mjs, copied here so this grammar stays inside the MIT kit's closure.
|
|
477
434
|
const WILL_BLOCK_TAGS = new Set('address article aside base basefont blockquote body caption center col colgroup dd details dialog dir div dl dt fieldset figcaption figure footer form frame frameset h1 h2 h3 h4 h5 h6 head header hr html iframe legend li link main menu menuitem nav noframes ol optgroup option p param search section summary table tbody td tfoot th thead title tr track ul'.split(' '));
|
|
478
435
|
const WILL_VOID_TAGS = new Set('area base br col embed hr img input link meta param source track wbr'.split(' '));
|
|
@@ -620,9 +577,6 @@ function willLeaf(stack) {
|
|
|
620
577
|
return kind === 'fence' || kind === 'pre' || kind === 'html' || kind === 'raw';
|
|
621
578
|
}
|
|
622
579
|
|
|
623
|
-
// `spans` are [start, end) of marker lines in `source` (the Will reader's own offsets).
|
|
624
|
-
// Returns the first quoted span, {start, end, where}, or null when every span may stand.
|
|
625
|
-
// `where` is `fence`, `raw-pre`, `raw-div`, `raw-table`, `raw-html` (any other raw HTML block) or `frontmatter`.
|
|
626
580
|
export function quotedWillSpan(source, spans) {
|
|
627
581
|
const text = String(source ?? '');
|
|
628
582
|
const marks = Array.isArray(spans) ? spans : [];
|
|
@@ -703,8 +657,6 @@ function pairSpanMarkers(source, kind, markers = null) {
|
|
|
703
657
|
return { runs, strays };
|
|
704
658
|
}
|
|
705
659
|
|
|
706
|
-
// Copy carries an arrow only when both complete anchor words are selected. A partial selection removes just
|
|
707
|
-
// its endpoint comments from the copied slice, including a comment clipped at the selection's boundary.
|
|
708
660
|
export function sliceInkArrows(source, start = 0, end = String(source ?? '').length, markers = null) {
|
|
709
661
|
const text = String(source ?? '');
|
|
710
662
|
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start || end > text.length) throw new RangeError('ink: invalid source slice');
|
|
@@ -728,8 +680,6 @@ export function sliceInkArrows(source, start = 0, end = String(source ?? '').len
|
|
|
728
680
|
return result + text.slice(at, end);
|
|
729
681
|
}
|
|
730
682
|
|
|
731
|
-
// The paste owns only its incoming markers: an orphan cannot adopt an existing document's endpoint, and a
|
|
732
|
-
// complete pair with an occupied number gets a new number in both openers without rewriting its stroke bytes.
|
|
733
683
|
export function prepareInkPaste(source, existing = '', markers = null) {
|
|
734
684
|
const text = String(source ?? ''), paired = pairInkMarkers(text, markers), edits = [];
|
|
735
685
|
for (const marker of paired.strays) if (marker.mark?.id != null || marker.arrowId != null) edits.push({ start: marker.start, end: marker.end, text: '' });
|
|
@@ -767,7 +717,6 @@ export function hasInkMarker(text) {
|
|
|
767
717
|
return scanInkMarkers(text).length > 0;
|
|
768
718
|
}
|
|
769
719
|
|
|
770
|
-
// The only spelling.
|
|
771
720
|
export function formatPageBreak() {
|
|
772
721
|
return PAGE_BREAK_MARKER;
|
|
773
722
|
}
|
package/dist/spec/md-source.mjs
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
import {splitOpeningFrontmatter} from './frontmatter.mjs';
|
|
3
3
|
|
|
4
|
-
// The standard parser owns literal regions and comment boundaries. Offsets are original UTF-16,
|
|
5
|
-
// including BOM/CRLF and list/quote prefixes. A private parser view observes rules; it changes no
|
|
6
|
-
// live rule or token. Call once per source, then ask isText(position) or isComment(start, end).
|
|
7
4
|
export function markdownSourcePositions(source, parser) {
|
|
8
5
|
if (typeof source !== 'string' || !parser) throw new TypeError('Source positions require text and its Markdown parser');
|
|
9
6
|
const {body, bodyOffset} = splitOpeningFrontmatter(source), lines = [bodyOffset];
|
|
@@ -17,8 +14,6 @@ export function markdownSourcePositions(source, parser) {
|
|
|
17
14
|
ruler.getRules = chain => parser.inline.ruler.getRules(chain).map(rule => (state, silent) => {
|
|
18
15
|
const start = state.pos, first = state.tokens.length;
|
|
19
16
|
const matched = rule(state, silent), token = state.tokens.at(-1);
|
|
20
|
-
// A pending text token may flush before this token. Never overwrite a nested rule's span
|
|
21
|
-
// with its enclosing link's range.
|
|
22
17
|
if (matched && !silent && state.tokens.length > first && !observed.has(token) &&
|
|
23
18
|
['code_inline', 'html_inline', 'image', 'footnote_ref'].includes(token?.type)) {
|
|
24
19
|
observed.set(token, {start, end: state.pos, source: state.src,
|
|
@@ -28,8 +23,6 @@ export function markdownSourcePositions(source, parser) {
|
|
|
28
23
|
}
|
|
29
24
|
return matched;
|
|
30
25
|
});
|
|
31
|
-
// A core rule may rewrite an inline token's content after its children were parsed (a task item drops
|
|
32
|
-
// its `[ ] `). Map rows from the text the inline rules read.
|
|
33
26
|
const read = new WeakMap();
|
|
34
27
|
inline.parse = function (text, md, env, children) { read.set(children, text); return parser.inline.parse.call(this, text, md, env, children); };
|
|
35
28
|
const {tokens, cells} = markdownTableSources(body, owner, {}, lines);
|
|
@@ -37,8 +30,6 @@ export function markdownSourcePositions(source, parser) {
|
|
|
37
30
|
for (const child of children || []) {
|
|
38
31
|
const span = observed.get(child);
|
|
39
32
|
if (!span || span.source !== content) continue;
|
|
40
|
-
// Inline footnotes are moved to a generated footer without line maps. Their parser-owned
|
|
41
|
-
// child tokens still stand inside the original ^[...] carrier, not in that footer.
|
|
42
33
|
if (span.footnote?.tokens) {
|
|
43
34
|
readInline(span.footnote.tokens, span.footnote.content, pos => rawOffset(span.start + 2 + pos));
|
|
44
35
|
} else if (child.type !== 'footnote_ref') {
|
|
@@ -58,9 +49,6 @@ export function markdownSourcePositions(source, parser) {
|
|
|
58
49
|
if (token.map && ['fence', 'code_block', 'html_block', 'reference_definition'].includes(token.type)) {
|
|
59
50
|
const start = lineAt(token.map[0]), end = lineAt(token.map[1]);
|
|
60
51
|
add(start, end);
|
|
61
|
-
// A comment-carrier block can contain several adjacent comments. Keep each carrier,
|
|
62
|
-
// including a closer surviving damage to its opener. Never promote nested comments or
|
|
63
|
-
// comment-shaped text in a raw tagged HTML block (pre, script, div, ...).
|
|
64
52
|
if (token.type === 'html_block' && /^\s*<!--/.test(token.content)) {
|
|
65
53
|
let at = source.indexOf('<!--', start);
|
|
66
54
|
while (at >= start && at < end) {
|
|
@@ -81,8 +69,6 @@ export function markdownSourcePositions(source, parser) {
|
|
|
81
69
|
for (let i = 0; i < rows.length; i++) {
|
|
82
70
|
const start = lineAt(token.map[0] + i), end = lineAt(token.map[0] + i + 1), line = source.slice(start, end).replace(/\0/g, '\ufffd');
|
|
83
71
|
let at = line.indexOf(rows[i]), lead = 0;
|
|
84
|
-
// A container that consumes part of a tab leaves the rest as spaces the source does not hold.
|
|
85
|
-
// They all stand for that tab.
|
|
86
72
|
if (at < 0) { const rest = rows[i].replace(/^ +/, ''); lead = rows[i].length - rest.length; at = line.indexOf(rest); if (!lead || line[at - 1] !== '\t') at = -1; }
|
|
87
73
|
if (at < 0) throw new Error('Markdown inline source mapping failed');
|
|
88
74
|
starts.push(local); spaces.push(lead); offsets.push(start + at - lead); local += rows[i].length + 1;
|
|
@@ -113,8 +99,6 @@ export function markdownSourcePositions(source, parser) {
|
|
|
113
99
|
return {isText, isComment: (start, end) => comments.get(start) === end, typography: typography.sort((a, b) => a.start - b.start)};
|
|
114
100
|
}
|
|
115
101
|
|
|
116
|
-
// Capture only table-cell provenance while the parser still owns container-adjusted row
|
|
117
|
-
// bounds. Looking for cell text in a whole row aliases literal pipes and quote/list markers.
|
|
118
102
|
export function markdownTableSources(source, parser, environment, lineStarts = null, parse = null) {
|
|
119
103
|
const cells = new WeakMap();
|
|
120
104
|
const read = owner => {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// An inert document asset, kept in Source and excluded from the reading surface.
|
|
3
|
-
// This is recognition only: interchange/docx.mjs admits the bounded JSON and native XML.
|
|
4
2
|
export const WORD_SIDECAR_OPEN = '<!-- rapier-word:v1\n';
|
|
5
3
|
export function isWordSidecarBlock(raw) {
|
|
6
4
|
return typeof raw === 'string' && /^<!-- rapier-word:v1\r?\n[^\r\n]*\r?\n-->[ \t\r\n]*$/.test(raw);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rapier-markdown-kit",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.9",
|
|
4
4
|
"description": "Read, render and write Self-contained Markdown: one .md file that carries its pictures, drawings and layout, and that any Markdown app can read. HTML renderer, stylesheet, text layout, Will/1 agent permissions, and an edit history with authorship and offline merge. MIT, no dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -40,17 +40,34 @@
|
|
|
40
40
|
"main": "./dist/kit/index.mjs",
|
|
41
41
|
"exports": {
|
|
42
42
|
".": "./dist/kit/index.mjs",
|
|
43
|
+
"./markdown-it": {
|
|
44
|
+
"types": "./dist/kit/markdown-it.d.mts",
|
|
45
|
+
"default": "./dist/kit/markdown-it.mjs"
|
|
46
|
+
},
|
|
43
47
|
"./layout": "./dist/kit/layout.mjs",
|
|
44
48
|
"./marks": "./dist/kit/marks.mjs",
|
|
45
49
|
"./model": "./dist/kit/model.mjs",
|
|
46
50
|
"./typography": "./dist/kit/typography.mjs",
|
|
47
|
-
"./will":
|
|
51
|
+
"./will": {
|
|
52
|
+
"types": "./dist/kit/will.d.mts",
|
|
53
|
+
"default": "./dist/kit/will.mjs"
|
|
54
|
+
},
|
|
48
55
|
"./assets": "./dist/kit/assets.mjs",
|
|
49
56
|
"./style.css": "./style.css",
|
|
50
57
|
"./package.json": "./package.json",
|
|
51
58
|
"./render": "./dist/kit/render-entry.mjs",
|
|
52
|
-
"./rapier-ledger":
|
|
53
|
-
|
|
59
|
+
"./rapier-ledger": {
|
|
60
|
+
"types": "./dist/kit/ledger.d.mts",
|
|
61
|
+
"default": "./dist/kit/ledger.mjs"
|
|
62
|
+
},
|
|
63
|
+
"./pdf": {
|
|
64
|
+
"types": "./dist/kit/pdf-source.d.mts",
|
|
65
|
+
"default": "./dist/kit/pdf-source.mjs"
|
|
66
|
+
},
|
|
67
|
+
"./ledger/carried": {
|
|
68
|
+
"types": "./dist/kit/ledger/carried.d.mts",
|
|
69
|
+
"default": "./dist/kit/ledger/carried.mjs"
|
|
70
|
+
}
|
|
54
71
|
},
|
|
55
72
|
"repository": {
|
|
56
73
|
"type": "git",
|
package/style.css
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
/* The reference scale's unit (docs/markdown-standard.md, "The reference scale"): 16 px at M, a length every reader can read
|
|
7
7
|
back as pixels (a planner scales its own lengths by it). */
|
|
8
8
|
@property --md-unit{syntax:'<length>';inherits:true;initial-value:16px}
|
|
9
|
+
@property --md-item-line{syntax:'<length>';inherits:true;initial-value:0px}
|
|
9
10
|
:root,.md-render{
|
|
10
11
|
--md-unit:1rem;
|
|
11
12
|
--md-hair:calc(var(--md-unit) / 16);
|
|
@@ -251,8 +252,9 @@
|
|
|
251
252
|
max-height: 9rem;
|
|
252
253
|
}
|
|
253
254
|
|
|
254
|
-
/* Type keeps the reference line model: body 17.6/28 px at M;
|
|
255
|
-
|
|
255
|
+
/* Type keeps the reference line model: body 17.6/28 px at M; h1 (2.2 times the body) occupies one
|
|
256
|
+
and three quarter body lines, h2 (1.75) and h3 (1.5) one and a half, h4 to h6 one, so a wrapped
|
|
257
|
+
heading sets about 1.3 of its size. Compact margins use the body's size: 8 px after prose, 12 after other
|
|
256
258
|
blocks, 20 before headings and 4 after them at M. Sibling margins collapse to their maximum;
|
|
257
259
|
the editor's derived neighbor gap carries the same boundary inside its measured wrapper. */
|
|
258
260
|
.md-render{
|
|
@@ -263,8 +265,8 @@
|
|
|
263
265
|
.md-render h1,.md-render h2,.md-render h3,.md-render h4,.md-render h5,.md-render h6{
|
|
264
266
|
font-family:var(--md-font-sans);letter-spacing:-0.01em;
|
|
265
267
|
line-height:var(--md-line);margin:var(--md-gap-heading-before) 0 var(--md-gap-heading-after);color:var(--md-heading-color);text-wrap:balance}
|
|
266
|
-
.md-render h1{font-size:calc(var(--md-text-body) * 2.
|
|
267
|
-
.md-render h2{font-size:calc(var(--md-text-body) * 1.
|
|
268
|
+
.md-render h1{font-size:calc(var(--md-text-body) * 2.2);line-height:calc(var(--md-line) * 1.75);font-weight:var(--md-fw-semibold)}
|
|
269
|
+
.md-render h2{font-size:calc(var(--md-text-body) * 1.75);line-height:calc(var(--md-line) * 1.5);font-weight:var(--md-fw-semibold)}
|
|
268
270
|
.md-render h3{font-size:calc(var(--md-text-body) * 1.5);line-height:calc(var(--md-line) * 1.5);font-weight:var(--md-fw-semibold)}
|
|
269
271
|
.md-render h4{font-size:calc(var(--md-text-body) * 1.25);font-weight:var(--md-fw-semibold)}
|
|
270
272
|
/* h5/h6 complete the six-level hierarchy: modest but owned (explicit size/weight/color), never
|
|
@@ -361,21 +363,28 @@
|
|
|
361
363
|
position:relative;
|
|
362
364
|
--md-marker:0px;
|
|
363
365
|
counter-increment:rapier-ol;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
+
/* The circle's size follows the item's own line (--md-item-line is registered, so 1lh resolves here on the li,
|
|
367
|
+
not in the marker's font): 2 units from the house line of 1.75 units up, never taller than the line at 1.48 units
|
|
368
|
+
or less, growing in between. The li makes room for it: at the house line exactly 0.15 units above
|
|
369
|
+
and 2 units' height, none once the circle fits the line. */
|
|
370
|
+
--md-item-line:1lh;
|
|
371
|
+
--md-ol-circle:min(2 * var(--md-unit), max(var(--md-item-line), 2 * var(--md-item-line) - 1.48 * var(--md-unit)));
|
|
372
|
+
--md-ol-top:calc(min(0.15 * var(--md-unit), 0.6 * max(0px, var(--md-ol-circle) - var(--md-item-line))) - min(0.125 * var(--md-unit), max(0px, var(--md-ol-circle) - var(--md-item-line)) / 2) + max(0px, var(--md-item-line) - var(--md-ol-circle)) / 2);
|
|
373
|
+
min-height:var(--md-ol-circle);
|
|
374
|
+
padding-top:min(0.15 * var(--md-unit), 0.6 * max(0px, var(--md-ol-circle) - var(--md-item-line)));
|
|
366
375
|
}
|
|
367
376
|
.md-render ol>li::before{
|
|
368
377
|
content:counter(rapier-ol);
|
|
369
378
|
position:absolute;
|
|
370
|
-
/*
|
|
371
|
-
inset-inline-start:calc(-
|
|
372
|
-
width:
|
|
379
|
+
/* Centered on the item's first line and on the dotted line's axis, 1.75 units before the item's text. */
|
|
380
|
+
inset-inline-start:calc(-1.75 * var(--md-unit) - var(--md-ol-circle) / 2 + var(--md-marker, 0px)); top:var(--md-ol-top);
|
|
381
|
+
width:var(--md-ol-circle); height:var(--md-ol-circle);
|
|
373
382
|
display:flex; align-items:center; justify-content:center;
|
|
374
383
|
border:2px solid var(--md-color-line);
|
|
375
384
|
border-radius:999px;
|
|
376
385
|
background:var(--md-color-bg);
|
|
377
386
|
color:var(--md-color-line);
|
|
378
|
-
font:var(--md-fw-semibold)
|
|
387
|
+
font:var(--md-fw-semibold) min(0.95 * var(--md-unit), 0.55 * var(--md-ol-circle))/1 var(--md-font-sans);
|
|
379
388
|
font-variant-numeric:tabular-nums;
|
|
380
389
|
box-sizing:border-box;
|
|
381
390
|
z-index:1;
|
|
@@ -386,8 +395,10 @@
|
|
|
386
395
|
position:absolute;
|
|
387
396
|
|
|
388
397
|
inset-inline-start:calc(calc(-1.75 * var(--md-unit)) - 1.5px + var(--md-marker, 0px));
|
|
389
|
-
|
|
390
|
-
|
|
398
|
+
/* From just below this circle into the next one: at least to the next item's top (--md-item-gap is the gap between
|
|
399
|
+
items the spacing settings give), and at least a quarter line, which the next circle covers. */
|
|
400
|
+
top:calc(var(--md-ol-top) + 1.0875 * var(--md-ol-circle));
|
|
401
|
+
bottom:calc(-1 * max(var(--md-item-gap, 0px), var(--md-line) / 4));
|
|
391
402
|
border-inline-start:3px dotted var(--md-color-line);
|
|
392
403
|
z-index:0;
|
|
393
404
|
}
|
|
@@ -411,7 +422,7 @@
|
|
|
411
422
|
.md-render li{margin:0 0 calc(var(--md-line) / 4)}
|
|
412
423
|
.md-render li:last-child{margin-bottom:0}
|
|
413
424
|
.md-render li>ul,.md-render li>ol{margin:calc(var(--md-line) / 4) 0 0}
|
|
414
|
-
.md-render a{color:var(--md-color-accent-text);text-decoration:underline;text-underline-offset:2px}
|
|
425
|
+
.md-render a{color:var(--md-color-accent-text);text-decoration:underline;text-underline-offset:2px;unicode-bidi:isolate}
|
|
415
426
|
/* The house focus (rapier-app.css) in the document and on the exported page: the underline thickens, no ring. */
|
|
416
427
|
.md-render a:focus-visible,.md-render ul.rapier-jump-list>li>a:focus-visible,.md-render .footnote-ref a:focus-visible,.md-render summary:focus-visible{outline:none;text-decoration:underline;text-decoration-thickness:3px;text-underline-offset:.2em}
|
|
417
428
|
|
|
@@ -425,37 +436,43 @@
|
|
|
425
436
|
.md-render .rapier-jump-label strong{font:inherit}
|
|
426
437
|
.md-render ul.rapier-jump-list{
|
|
427
438
|
--md-jump-gap:calc(.9 * var(--md-unit));
|
|
439
|
+
--md-jump-pad:calc(.18 * var(--md-unit));
|
|
440
|
+
--md-jump-circle:10px;
|
|
428
441
|
--md-jump-marker-inline-start:calc(-1.18 * var(--md-unit));
|
|
429
|
-
--md-jump-marker-center:1em;
|
|
430
442
|
list-style:none;
|
|
431
443
|
padding-inline-start:calc(1.85 * var(--md-unit));
|
|
432
444
|
margin:0 0 var(--md-gap-block);
|
|
433
445
|
}
|
|
434
446
|
.md-render ul.rapier-jump-list>li{
|
|
435
447
|
position:relative;
|
|
448
|
+
/* The circle sits on the first line's center: --md-item-line is registered, so 1lh resolves here on the li. */
|
|
449
|
+
--md-item-line:1lh;
|
|
450
|
+
--md-jump-circle-top:calc(var(--md-jump-pad) + (var(--md-item-line) - var(--md-jump-circle)) / 2);
|
|
436
451
|
margin:0 0 var(--md-jump-gap);
|
|
437
|
-
padding:
|
|
452
|
+
padding:var(--md-jump-pad) 0 var(--md-jump-pad) calc(.15 * var(--md-unit));
|
|
438
453
|
}
|
|
439
454
|
.md-render ul.rapier-jump-list>li:last-child{margin-bottom:0}
|
|
440
455
|
.md-render ul.rapier-jump-list>li::before{
|
|
441
456
|
content:'';
|
|
442
457
|
position:absolute;
|
|
443
458
|
inset-inline-start:var(--md-jump-marker-inline-start);
|
|
444
|
-
top:
|
|
445
|
-
width:
|
|
459
|
+
top:var(--md-jump-circle-top);
|
|
460
|
+
width:var(--md-jump-circle);height:var(--md-jump-circle);
|
|
446
461
|
box-sizing:border-box;
|
|
447
462
|
border:2px solid var(--md-color-accent-text);
|
|
448
463
|
background:var(--md-color-bg);
|
|
449
464
|
border-radius:50%;
|
|
450
465
|
z-index:1;
|
|
451
466
|
}
|
|
467
|
+
/* The dotted line is a numbered list's (3px, the text's black or white): from below this circle to the next circle's
|
|
468
|
+
center, which covers its end; none after the last item. --md-item-gap is the gap the spacing settings give. */
|
|
452
469
|
.md-render ul.rapier-jump-list>li:not(:last-child)::after{
|
|
453
470
|
content:'';
|
|
454
471
|
position:absolute;
|
|
455
|
-
inset-inline-start:calc(var(--md-jump-marker-inline-start) +
|
|
456
|
-
top:var(--md-jump-
|
|
457
|
-
bottom:calc(
|
|
458
|
-
border-inline-start:
|
|
472
|
+
inset-inline-start:calc(var(--md-jump-marker-inline-start) + var(--md-jump-circle) / 2 - 1.5px);
|
|
473
|
+
top:calc(var(--md-jump-circle-top) + var(--md-jump-circle));
|
|
474
|
+
bottom:calc(-1 * (var(--md-item-gap, var(--md-jump-gap)) + var(--md-jump-circle-top) + var(--md-jump-circle) / 2));
|
|
475
|
+
border-inline-start:3px dotted var(--md-color-line);
|
|
459
476
|
z-index:0;
|
|
460
477
|
}
|
|
461
478
|
.md-render ul.rapier-jump-list>li>a{
|