rapier-markdown-kit 1.2.12 → 1.2.13

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.
@@ -1217,7 +1217,7 @@ function* _rapierProjectPortableNodesSteps(semanticRoot, options) {
1217
1217
  const alignment = paragraphLayout?.align || columnAlignment;
1218
1218
  const imageLayout = element.tagName === 'IMG' ? globalThis.RapierMarkdownLayout.parseLayoutAttribute(element.getAttribute('data-rapier-image-layout')) : null;
1219
1219
  const measurements = keepLayoutData && /^(?:P|H[1-6]|LI|OL|UL)$/.test(element.tagName)
1220
- ? ['text-indent', 'margin-inline-start'].map(property => [property, element.style.getPropertyValue(property)]) : [];
1220
+ ? ['text-indent', 'margin-inline-start', 'margin-top', 'margin-bottom', 'line-height'].map(property => [property, element.style.getPropertyValue(property)]) : [];
1221
1221
  if (keepLayoutData && element.tagName === 'P' && element.closest('table') && element.style.getPropertyValue('hyphens').trim() === 'auto')
1222
1222
  measurements.push(['hyphens', 'auto']);
1223
1223
  if (keepLayoutData && element.closest('pre')) for (const property of ['color', 'font-weight', 'font-style'])
@@ -1257,6 +1257,11 @@ function* _rapierProjectPortableNodesSteps(semanticRoot, options) {
1257
1257
  for (const [property, value] of measurements) if (value) element.style.setProperty(property, value);
1258
1258
  for (const [property, value] of runTypography) if (value) element.style.setProperty(property, value);
1259
1259
  if (alignment) element.style.textAlign = alignment;
1260
+ if (paragraphLayout && /^(?:P|H[1-6])$/.test(element.tagName)) {
1261
+ if (paragraphLayout.line != null) element.style.lineHeight = String(paragraphLayout.line).replace(/\+$/, '');
1262
+ if (paragraphLayout.before != null) element.style.marginTop = paragraphLayout.before + 'pt';
1263
+ if (paragraphLayout.after != null) element.style.marginBottom = paragraphLayout.after + 'pt';
1264
+ }
1260
1265
  if (!keepLayoutData && element.tagName === 'P' && !element.closest('table,pre')) {
1261
1266
  if (paragraphLayout?.hyphens === 'auto') { element.style.hyphens = 'auto'; element.style.textWrap = 'pretty'; }
1262
1267
  if (paragraphLayout?.hanging === 'on') element.style.setProperty('hanging-punctuation', 'first allow-end');
@@ -11,7 +11,7 @@ export const SPACING_PRESETS = Object.freeze({compact: .7, standard: 1, roomy: 1
11
11
  const SPACING_SELECTORS = Object.freeze({paragraph: 'p', h1: 'h1', h2: 'h2', h3: 'h3', h4: 'h4', h5: 'h5', h6: 'h6',
12
12
  list: ':is(ul,ol)', item: 'li', table: ':is(table,.table-scroll-wrap)', picture: 'p[data-md-picture]',
13
13
  drawing: ':is(.diagram-block,p[data-md-picture]:has(img[data-md-svg],img[src^="data:image/svg+xml"]))', quote: 'blockquote',
14
- code: 'pre', rule: 'hr:not(.footnotes-sep):not([data-md-break])', footnote: '.footnotes', toc: '.rapier-jump-list'});
14
+ code: 'pre', rule: 'hr:not(.footnotes-sep):not([data-md-break])', footnote: '.footnotes', toc: 'ul.rapier-jump-list'});
15
15
  const HEADING_LINE = [1.75 / 2.2, 1.5 / 1.75, 1.2 / 1.5, 1 / 1.25, .9 / 1.1, .82];
16
16
 
17
17
  export function normalizeDocumentSpacing(value) {
@@ -72,7 +72,10 @@ export function documentSpacingCss(settings, scope = '.md-render', defaults = fa
72
72
  css += selector + '{margin-block:' + length(row.before) + ' ' + length(row.after) + ';line-height:' + row.line + '}';
73
73
  if (settings?.word && /^h[1-6]$/.test(type)) css += selector + '{font-size:' + length(documentHeadingSize(settings, Number(type[1]))) + '}';
74
74
  }
75
- css += scope + ' :is(li,td,th,blockquote)>:last-child:not(input){margin-bottom:0}' +
75
+ css += scope + '>:first-child,' + scope + ' :is(blockquote,li,td,th,dd,.callout,details)>:first-child:not(input){margin-top:0}' +
76
+ scope + ' :is(li,td,th,blockquote,details,dd)>:last-child:not(input){margin-bottom:0}' +
77
+ scope + ' :is(p,h1,h2,h3,h4,h5,h6)[data-md-before]{margin-top:var(--md-before)}' +
78
+ scope + ' :is(p,h1,h2,h3,h4,h5,h6)[data-md-after]{margin-bottom:var(--md-after)}' +
76
79
  scope + ' li:last-child{margin-bottom:0}' + scope + ' li>:is(ul,ol){margin-bottom:0}' +
77
80
  scope + ' .table-scroll-wrap>table{margin-block:0}' +
78
81
  scope + ' pre{font-size:calc(var(--md-text-body) * .7386)}' +
@@ -1,11 +1,22 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
 
3
- export const fields = new Set(['align', 'width', 'lines', 'wrap', 'x', 'y', 'rotate', 'opacity', 'first', 'indent', 'marker', 'lock', 'hyphens', 'caps', 'hanging']);
3
+ export const fields = new Set(['align', 'width', 'lines', 'wrap', 'x', 'y', 'rotate', 'opacity', 'first', 'indent', 'marker', 'lock', 'hyphens', 'caps', 'hanging', 'before', 'after', 'line']);
4
+ export const SPACING_FIELDS = Object.freeze(['before', 'after', 'line']);
4
5
  export const alignments = new Set(['left', 'center', 'right', 'justify']);
5
6
  export const wraps = new Set(['around', 'box', 'behind', 'front']);
6
7
  const has = (value, key) => Object.hasOwn(value, key);
7
8
  const percent = value => typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 100;
8
9
  const lineCount = value => Number.isInteger(value) && value >= 1 && value <= 12;
10
+ const hundredths = value => Math.abs(value * 100 - Math.round(value * 100)) < 1e-9;
11
+ const spaceValue = value => typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 720 && hundredths(value);
12
+ const linePoint = value => {
13
+ if (typeof value !== 'string') return null;
14
+ const match = /^(?:0|[1-9]\d{0,2})(?:\.\d{1,2})?pt(\+)?$/.exec(value);
15
+ if (!match) return null;
16
+ const points = Number(value.slice(0, match[1] ? -3 : -2));
17
+ return points > 0 && points <= 720 && hundredths(points) && value === decimal(points) + (match[1] ? 'pt+' : 'pt') ? {points, atLeast: !!match[1]} : null;
18
+ };
19
+ const lineValue = value => typeof value === 'number' ? Number.isFinite(value) && value >= .5 && value <= 4 && hundredths(value) : !!linePoint(value);
9
20
  export const INDENT_LIMIT = 64;
10
21
  export const indentLevel = value => typeof value === 'number' && Number.isFinite(value) && value !== 0 &&
11
22
  Math.abs(value) <= INDENT_LIMIT && Number(value.toFixed(6)) === value;
@@ -34,6 +45,8 @@ export function validLayout(value) {
34
45
  (!has(value, 'hyphens') || value.hyphens === 'auto') &&
35
46
  (!has(value, 'caps') || value.caps === 'small') &&
36
47
  (!has(value, 'hanging') || value.hanging === 'on') &&
48
+ (!has(value, 'before') || spaceValue(value.before)) && (!has(value, 'after') || spaceValue(value.after)) &&
49
+ (!has(value, 'line') || lineValue(value.line)) &&
37
50
  !(has(value, 'align') && (has(value, 'wrap') || has(value, 'x')));
38
51
  }
39
52
 
@@ -68,6 +81,15 @@ export function parseLayout(comment) {
68
81
  } else if (key === 'lines') {
69
82
  if (!/^[1-9]\d?$/.test(raw)) return null;
70
83
  value.lines = Number(raw);
84
+ } else if (key === 'before' || key === 'after') {
85
+ if (!/^(?:0|[1-9]\d{0,2})(?:\.\d{1,2})?pt$/.test(raw)) return null;
86
+ value[key] = Number(raw.slice(0, -2));
87
+ } else if (key === 'line') {
88
+ if (/^[0-4](?:\.\d{1,2})?$/.test(raw)) value.line = Number(raw);
89
+ else if (/^(?:0|[1-9]\d{0,2})(?:\.\d{1,2})?pt\+?$/.test(raw)) {
90
+ const plus = raw.endsWith('+');
91
+ value.line = decimal(Number(raw.slice(0, plus ? -3 : -2))) + (plus ? 'pt+' : 'pt');
92
+ } else return null;
71
93
  } else if (key === 'first' || key === 'indent' || key === 'marker') {
72
94
  if (!/^-?(?:0|[1-9]\d?)(?:\.\d{1,6})?$/.test(raw)) return null;
73
95
  value[key] = Number(raw);
@@ -94,19 +116,47 @@ export function textStyle(value, {portable = false, pointSize, indentOffset = 0}
94
116
  'calc(' + measure + ' * var(--md-text-body))');
95
117
  });
96
118
  if (portable && value.hyphens === 'auto') css.push('hyphens:auto');
119
+ if (has(value, 'line')) {
120
+ const point = linePoint(value.line);
121
+ const unit = point && 'calc(' + decimal(Number((point.points / 12).toFixed(6))) + ' * var(--md-unit))';
122
+ css.push('line-height:' + (!point ? decimal(value.line) : portable ? decimal(point.points) + 'pt' : point.atLeast ? 'max(1.2em, ' + unit + ')' : unit));
123
+ }
124
+ for (const [key, prop] of [['before', 'margin-top'], ['after', 'margin-bottom']])
125
+ if (has(value, key)) css.push(portable ? prop + ':' + decimal(value[key]) + 'pt' :
126
+ '--md-' + key + ':calc(' + decimal(Number((value[key] / 12).toFixed(6))) + ' * var(--md-unit))');
97
127
  return css.join(';');
98
128
  }
99
129
 
100
130
  export function textLayout(style, {listMarker = false, pointSize, offset = 0} = {}) {
131
+ const get = property => (typeof style?.getPropertyValue === 'function' ? style.getPropertyValue(property) :
132
+ style?.[property] ?? style?.[property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())] ?? '').trim();
101
133
  const value = {};
102
134
  for (const [key, property] of (listMarker ? [['marker', 'margin-inline-start']] : [['first', 'text-indent'], ['indent', 'margin-inline-start']])) {
103
- const raw = style?.getPropertyValue(property).trim() || '';
135
+ const raw = get(property);
104
136
  const match = /^(-?(?:0|[1-9]\d*)(?:\.\d{1,6})?)(em|pt)$/.exec(raw);
105
137
  const level = Number(((match ? Number(match[1]) / (match[2] === 'pt' ? pointSize : 1) / 2 : 0) +
106
138
  (key === 'first' ? 0 : offset)).toFixed(6));
107
139
  if (Number.isFinite(level) && level) value[key] = level;
108
140
  }
109
- if (!listMarker && style?.getPropertyValue('hyphens').trim() === 'auto') value.hyphens = 'auto';
141
+ if (!listMarker) {
142
+ if (get('hyphens') === 'auto') value.hyphens = 'auto';
143
+ for (const [key, property] of [['before', 'margin-top'], ['after', 'margin-bottom']]) {
144
+ const raw = get(property);
145
+ if (/^(?:0|[1-9]\d{0,2})(?:\.\d{1,2})?pt$/i.test(raw)) {
146
+ const points = Number(raw.slice(0, -2));
147
+ if (spaceValue(points)) value[key] = points;
148
+ }
149
+ }
150
+ const line = get('line-height');
151
+ if (/^[0-4](?:\.\d{1,2})?$/.test(line)) {
152
+ const factor = Number(line);
153
+ if (lineValue(factor)) value.line = factor;
154
+ } else if (/^(?:0|[1-9]\d{0,2})(?:\.\d{1,2})?pt\+?$/i.test(line)) {
155
+ const atLeast = line.endsWith('+'), points = Number(line.slice(0, atLeast ? -3 : -2));
156
+ const formatted = decimal(points) + (atLeast ? 'pt+' : 'pt');
157
+ if (lineValue(formatted)) value.line = formatted;
158
+ }
159
+ }
110
160
  return value;
111
161
  }
112
162
 
@@ -173,7 +223,8 @@ export function formatLayout(value) {
173
223
  if (has(value, key) && !(key === 'y' && value.y === 0) && !(key === 'rotate' && value.rotate === 0) &&
174
224
  !(key === 'opacity' && value.opacity === 100)) pairs.push(key + '=' +
175
225
  (key === 'width' || key === 'x' || key === 'opacity' ? decimal(value[key]) + '%' : key === 'y' ? decimal(value.y) + 'em' :
176
- key === 'rotate' ? decimal(value.rotate) + 'deg' : value[key]));
226
+ key === 'rotate' ? decimal(value.rotate) + 'deg' : key === 'before' || key === 'after' ? decimal(value[key]) + 'pt' :
227
+ key === 'line' ? (typeof value.line === 'number' ? decimal(value.line) : value.line) : value[key]));
177
228
  }
178
229
  return pairs.length ? '<!--md-layout:v1 ' + pairs.join(' ') + '-->' : '';
179
230
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rapier-markdown-kit",
3
- "version": "1.2.12",
3
+ "version": "1.2.13",
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",
package/style.css CHANGED
@@ -300,6 +300,8 @@
300
300
  .md-render h6{font-size:var(--md-text-body);font-weight:var(--md-fw-semibold);color:var(--md-heading-muted-color)}
301
301
  .md-render p{margin:0 0 var(--md-gap-paragraph)}
302
302
  .md-render p[data-md-picture]{margin-bottom:var(--md-gap-block)}
303
+ .md-render :is(p,h1,h2,h3,h4,h5,h6)[data-md-before]{--md-before:0px;margin-top:var(--md-before)}
304
+ .md-render :is(p,h1,h2,h3,h4,h5,h6)[data-md-after]{--md-after:0px;margin-bottom:var(--md-after)}
303
305
  .md-render ul,.md-render ol{margin:0 0 var(--md-gap-block);padding-inline-start:var(--md-space-5)}
304
306
  .md-render ul{list-style:none}
305
307
  .md-render ul>li{position:relative;--md-marker:0px}