@diagrammo/dgmo 0.39.0 → 0.40.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/dist/advanced.cjs +324 -116
- package/dist/advanced.js +324 -116
- package/dist/auto.cjs +325 -117
- package/dist/auto.js +92 -92
- package/dist/auto.mjs +325 -117
- package/dist/cli.cjs +115 -115
- package/dist/index.cjs +502 -171
- package/dist/index.js +502 -171
- package/gallery/fixtures/block.dgmo +16 -0
- package/gallery/fixtures/event-line.dgmo +26 -0
- package/package.json +1 -1
- package/src/diagnostics.ts +0 -0
- package/src/echarts.ts +5 -0
- package/src/error-card.ts +200 -0
- package/src/event-line/parser.ts +91 -1
- package/src/event-line/renderer.ts +350 -139
- package/src/index.ts +7 -30
- package/src/journey-map/renderer.ts +50 -18
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
block Web Service Architecture
|
|
2
|
+
|
|
3
|
+
tag Layer as l
|
|
4
|
+
Edge blue
|
|
5
|
+
Service green
|
|
6
|
+
Data orange
|
|
7
|
+
|
|
8
|
+
[Clients] l: Edge
|
|
9
|
+
[Browser] [Mobile] [CLI]
|
|
10
|
+
|
|
11
|
+
[Backend] l: Service
|
|
12
|
+
[Auth] [Orders]
|
|
13
|
+
[Inventory] [Billing]
|
|
14
|
+
|
|
15
|
+
[Data] l: Data collapsed
|
|
16
|
+
[Postgres] [Redis]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
event-line A Short History of the Web
|
|
2
|
+
no-scale
|
|
3
|
+
|
|
4
|
+
tag Theme as t
|
|
5
|
+
Protocol blue
|
|
6
|
+
Browser green
|
|
7
|
+
Platform purple
|
|
8
|
+
Standard orange
|
|
9
|
+
|
|
10
|
+
[The Early Web]
|
|
11
|
+
1991 WorldWideWeb t: Protocol
|
|
12
|
+
Tim Berners-Lee publishes the first website at **CERN**.
|
|
13
|
+
1993 Mosaic t: Browser
|
|
14
|
+
The first popular *graphical* browser.
|
|
15
|
+
|
|
16
|
+
[The Standards Era] collapsed: true
|
|
17
|
+
1995 JavaScript t: Platform
|
|
18
|
+
Brendan Eich writes the language in ten days.
|
|
19
|
+
1996 CSS t: Standard
|
|
20
|
+
Styling splits from structure.
|
|
21
|
+
|
|
22
|
+
[The App Era]
|
|
23
|
+
2005 Ajax t: Platform
|
|
24
|
+
`XMLHttpRequest` updates pages without a reload.
|
|
25
|
+
2014 HTML5 t: Standard
|
|
26
|
+
The living standard — video, canvas, semantics.
|
package/package.json
CHANGED
package/src/diagnostics.ts
CHANGED
|
Binary file
|
package/src/echarts.ts
CHANGED
|
@@ -283,6 +283,9 @@ const CHART_BASE: Pick<
|
|
|
283
283
|
tooltip: { show: false },
|
|
284
284
|
};
|
|
285
285
|
const CHART_BORDER_WIDTH = 2;
|
|
286
|
+
// Scatter bubbles render semi-transparent so overlapping points stay visible.
|
|
287
|
+
// Emphasis bumps back to full opacity on hover (see emphasisConfig).
|
|
288
|
+
const SCATTER_FILL_OPACITY = 0.65;
|
|
286
289
|
|
|
287
290
|
// ============================================================
|
|
288
291
|
// Parser
|
|
@@ -1930,6 +1933,7 @@ function buildScatterOption(
|
|
|
1930
1933
|
}),
|
|
1931
1934
|
borderColor: catColor,
|
|
1932
1935
|
borderWidth: CHART_BORDER_WIDTH,
|
|
1936
|
+
opacity: SCATTER_FILL_OPACITY,
|
|
1933
1937
|
},
|
|
1934
1938
|
label: labelConfig,
|
|
1935
1939
|
emphasis: emphasisConfig,
|
|
@@ -1961,6 +1965,7 @@ function buildScatterOption(
|
|
|
1961
1965
|
{
|
|
1962
1966
|
type: 'scatter' as const,
|
|
1963
1967
|
data,
|
|
1968
|
+
itemStyle: { opacity: SCATTER_FILL_OPACITY },
|
|
1964
1969
|
label: labelConfig,
|
|
1965
1970
|
emphasis: emphasisConfig,
|
|
1966
1971
|
blur: blurConfig,
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Error Card — fallback image rendered when a diagram can't parse
|
|
3
|
+
// ============================================================
|
|
4
|
+
//
|
|
5
|
+
// When `render(text, { onError: 'svg' })` (the default) hits parse errors,
|
|
6
|
+
// hosts that embed dgmo — remark/astro/docusaurus/fumadocs, Obsidian, the
|
|
7
|
+
// web editor — show whatever SVG we return in place of the diagram. A blank
|
|
8
|
+
// or cryptic box is a dead end for the author, so we render a friendly card:
|
|
9
|
+
// a frown mark, the dgmo wordmark, and per-error the line number, the
|
|
10
|
+
// message, and the actual offending source line with a caret at the column.
|
|
11
|
+
//
|
|
12
|
+
// Pure string builder — no DOM, so it works in every consumer (SSR, browser,
|
|
13
|
+
// resvg→PNG in the CLI). resvg has no color-mix(); all colors are pre-resolved.
|
|
14
|
+
|
|
15
|
+
import { truncateText, measureText } from './utils/text-measure';
|
|
16
|
+
import { FONT_FAMILY } from './fonts';
|
|
17
|
+
import type { PaletteConfig } from './palettes/types';
|
|
18
|
+
import type { Theme } from './themes';
|
|
19
|
+
import type { DgmoError } from './diagnostics';
|
|
20
|
+
|
|
21
|
+
const MONO_FAMILY =
|
|
22
|
+
"ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace";
|
|
23
|
+
|
|
24
|
+
const W = 660;
|
|
25
|
+
const PAD = 24;
|
|
26
|
+
const CONTENT_W = W - PAD * 2;
|
|
27
|
+
const MAX_ERRORS = 4;
|
|
28
|
+
// Monospace advance ≈ 0.6em — used to place the caret under the bad column.
|
|
29
|
+
const MONO_CH = 0.6;
|
|
30
|
+
const CODE_FS = 12.5;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Render a self-contained SVG error card for a failed diagram parse.
|
|
34
|
+
*
|
|
35
|
+
* @param errors Error-severity diagnostics (line-numbered).
|
|
36
|
+
* @param source Original DGMO source, used to quote the offending lines.
|
|
37
|
+
*/
|
|
38
|
+
export function renderErrorCard(
|
|
39
|
+
errors: DgmoError[],
|
|
40
|
+
source: string,
|
|
41
|
+
palette: PaletteConfig,
|
|
42
|
+
theme?: Theme
|
|
43
|
+
): string {
|
|
44
|
+
const c = palette[theme === 'dark' ? 'dark' : 'light'];
|
|
45
|
+
const isTransparent = theme === 'transparent';
|
|
46
|
+
const card = c.surface;
|
|
47
|
+
const codeBg = c.bg;
|
|
48
|
+
const border = c.border;
|
|
49
|
+
const danger = c.destructive ?? c.colors.red;
|
|
50
|
+
const text = c.text;
|
|
51
|
+
const muted = c.textMuted;
|
|
52
|
+
const srcLines = source.split('\n');
|
|
53
|
+
|
|
54
|
+
const shown = errors.slice(0, MAX_ERRORS);
|
|
55
|
+
const overflow = errors.length - shown.length;
|
|
56
|
+
|
|
57
|
+
const parts: string[] = [];
|
|
58
|
+
let y = PAD + 8;
|
|
59
|
+
|
|
60
|
+
// ── Header: frown mark + title, dgmo wordmark right ────────
|
|
61
|
+
parts.push(frownMark(PAD + 14, y + 12, danger));
|
|
62
|
+
parts.push(
|
|
63
|
+
`<text x="${PAD + 38}" y="${y + 11}" fill="${text}" font-family="${FONT_FAMILY}" font-size="16" font-weight="700">Couldn't render this diagram</text>`,
|
|
64
|
+
`<text x="${W - PAD}" y="${y + 10}" text-anchor="end" fill="${muted}" font-family="${FONT_FAMILY}" font-size="12" font-weight="600" letter-spacing="0.5">dgmo</text>`
|
|
65
|
+
);
|
|
66
|
+
y += 24;
|
|
67
|
+
const errLabel = errors.length === 1 ? '1 error' : `${errors.length} errors`;
|
|
68
|
+
parts.push(
|
|
69
|
+
`<text x="${PAD + 38}" y="${y + 4}" fill="${muted}" font-family="${FONT_FAMILY}" font-size="12.5">${errLabel} — fix the lines below and re-render</text>`
|
|
70
|
+
);
|
|
71
|
+
y += 22;
|
|
72
|
+
parts.push(
|
|
73
|
+
`<line x1="${PAD}" y1="${y}" x2="${W - PAD}" y2="${y}" stroke="${border}" stroke-width="1"/>`
|
|
74
|
+
);
|
|
75
|
+
y += 24;
|
|
76
|
+
|
|
77
|
+
// ── Per-error blocks ───────────────────────────────────────
|
|
78
|
+
for (const e of shown) {
|
|
79
|
+
const hasLine = e.line > 0;
|
|
80
|
+
|
|
81
|
+
// Line badge (skip when line is unknown / 0), with the message inline.
|
|
82
|
+
let msgX = PAD;
|
|
83
|
+
if (hasLine) {
|
|
84
|
+
const label = e.column ? `Line ${e.line}:${e.column}` : `Line ${e.line}`;
|
|
85
|
+
const badgeW = 14 + label.length * 7;
|
|
86
|
+
parts.push(
|
|
87
|
+
`<rect x="${PAD}" y="${y - 13}" width="${badgeW}" height="19" rx="4" fill="${danger}"/>`,
|
|
88
|
+
`<text x="${PAD + badgeW / 2}" y="${y + 1}" text-anchor="middle" fill="${c.bg}" font-family="${FONT_FAMILY}" font-size="11.5" font-weight="700">${escapeXml(label)}</text>`
|
|
89
|
+
);
|
|
90
|
+
msgX = PAD + badgeW + 12;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Message: first line sits to the right of the badge, continuation lines
|
|
94
|
+
// wrap to the full content width on the rows below.
|
|
95
|
+
const lines = wrapMessage(e.message, CONTENT_W - (msgX - PAD), CONTENT_W);
|
|
96
|
+
lines.forEach((line, i) => {
|
|
97
|
+
parts.push(
|
|
98
|
+
`<text x="${i === 0 ? msgX : PAD}" y="${y + 1}" fill="${text}" font-family="${FONT_FAMILY}" font-size="13">${escapeXml(line)}</text>`
|
|
99
|
+
);
|
|
100
|
+
if (i < lines.length - 1) y += 18;
|
|
101
|
+
});
|
|
102
|
+
y += 24;
|
|
103
|
+
|
|
104
|
+
// Offending source line, monospace, with a caret under the column.
|
|
105
|
+
const raw = hasLine ? srcLines[e.line - 1] : undefined;
|
|
106
|
+
if (raw !== undefined && raw.trim().length > 0) {
|
|
107
|
+
const gutter = `${e.line}`;
|
|
108
|
+
const gutterW = gutter.length * CODE_FS * MONO_CH + 16;
|
|
109
|
+
const codeX = PAD + gutterW;
|
|
110
|
+
const codeMaxW = CONTENT_W - gutterW - 12;
|
|
111
|
+
const codeText = truncateText(
|
|
112
|
+
raw.replace(/\t/g, ' '),
|
|
113
|
+
CODE_FS,
|
|
114
|
+
codeMaxW
|
|
115
|
+
);
|
|
116
|
+
const hasCaret = e.column !== undefined && e.column > 0;
|
|
117
|
+
const blockH = hasCaret ? 40 : 26;
|
|
118
|
+
parts.push(
|
|
119
|
+
`<rect x="${PAD}" y="${y - 14}" width="${CONTENT_W}" height="${blockH}" rx="5" fill="${codeBg}" stroke="${border}" stroke-width="1"/>`,
|
|
120
|
+
`<line x1="${codeX - 8}" y1="${y - 14}" x2="${codeX - 8}" y2="${y - 14 + blockH}" stroke="${border}" stroke-width="1"/>`,
|
|
121
|
+
`<text x="${PAD + 8}" y="${y + 3}" fill="${muted}" font-family="${MONO_FAMILY}" font-size="${CODE_FS}">${escapeXml(gutter)}</text>`,
|
|
122
|
+
`<text x="${codeX}" y="${y + 3}" fill="${text}" font-family="${MONO_FAMILY}" font-size="${CODE_FS}" xml:space="preserve">${escapeXml(codeText)}</text>`
|
|
123
|
+
);
|
|
124
|
+
if (hasCaret) {
|
|
125
|
+
const caretX = codeX + (e.column! - 1) * CODE_FS * MONO_CH;
|
|
126
|
+
parts.push(
|
|
127
|
+
`<text x="${caretX}" y="${y + 17}" fill="${danger}" font-family="${MONO_FAMILY}" font-size="${CODE_FS}" font-weight="700">^</text>`
|
|
128
|
+
);
|
|
129
|
+
y += 14;
|
|
130
|
+
}
|
|
131
|
+
y += 24;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
y += 14;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (overflow > 0) {
|
|
138
|
+
parts.push(
|
|
139
|
+
`<text x="${PAD}" y="${y}" fill="${muted}" font-family="${FONT_FAMILY}" font-size="12.5" font-style="italic">+ ${overflow} more error${overflow === 1 ? '' : 's'}</text>`
|
|
140
|
+
);
|
|
141
|
+
y += 22;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const height = Math.round(y + PAD - 14);
|
|
145
|
+
const pageBg = isTransparent
|
|
146
|
+
? ''
|
|
147
|
+
: `<rect width="100%" height="100%" fill="${c.bg}"/>`;
|
|
148
|
+
|
|
149
|
+
return [
|
|
150
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${height}" viewBox="0 0 ${W} ${height}" font-family="${FONT_FAMILY}">`,
|
|
151
|
+
pageBg,
|
|
152
|
+
`<rect x="0.5" y="0.5" width="${W - 1}" height="${height - 1}" rx="10" fill="${card}" stroke="${border}" stroke-width="1"/>`,
|
|
153
|
+
parts.join(''),
|
|
154
|
+
`</svg>`,
|
|
155
|
+
].join('');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Greedy word-wrap where the first line is narrower (it shares its row with
|
|
159
|
+
// the line badge) and continuation lines use the full content width.
|
|
160
|
+
function wrapMessage(
|
|
161
|
+
message: string,
|
|
162
|
+
firstWidth: number,
|
|
163
|
+
fullWidth: number
|
|
164
|
+
): string[] {
|
|
165
|
+
const words = message.split(/\s+/).filter(Boolean);
|
|
166
|
+
if (words.length === 0) return [''];
|
|
167
|
+
const lines: string[] = [];
|
|
168
|
+
let cur = '';
|
|
169
|
+
for (const word of words) {
|
|
170
|
+
const limit = lines.length === 0 ? firstWidth : fullWidth;
|
|
171
|
+
const test = cur ? `${cur} ${word}` : word;
|
|
172
|
+
if (measureText(test, 13) <= limit || !cur) {
|
|
173
|
+
cur = test;
|
|
174
|
+
} else {
|
|
175
|
+
lines.push(cur);
|
|
176
|
+
cur = word;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (cur) lines.push(cur);
|
|
180
|
+
return lines;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Frown face: ringed circle, two eyes, a downturned mouth.
|
|
184
|
+
function frownMark(cx: number, cy: number, color: string): string {
|
|
185
|
+
const r = 12;
|
|
186
|
+
return [
|
|
187
|
+
`<circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="${color}" stroke-width="2"/>`,
|
|
188
|
+
`<circle cx="${cx - 4.5}" cy="${cy - 3}" r="1.6" fill="${color}"/>`,
|
|
189
|
+
`<circle cx="${cx + 4.5}" cy="${cy - 3}" r="1.6" fill="${color}"/>`,
|
|
190
|
+
`<path d="M ${cx - 5} ${cy + 6} Q ${cx} ${cy + 1} ${cx + 5} ${cy + 6}" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round"/>`,
|
|
191
|
+
].join('');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function escapeXml(s: string): string {
|
|
195
|
+
return s
|
|
196
|
+
.replace(/&/g, '&')
|
|
197
|
+
.replace(/</g, '<')
|
|
198
|
+
.replace(/>/g, '>')
|
|
199
|
+
.replace(/"/g, '"');
|
|
200
|
+
}
|
package/src/event-line/parser.ts
CHANGED
|
@@ -55,6 +55,7 @@ export const EVENT_LINE_DIAGNOSTIC_CODES = {
|
|
|
55
55
|
NO_EVENTS: 'E_EVENT_LINE_NO_EVENTS',
|
|
56
56
|
BAD_DATE: 'E_EVENT_LINE_BAD_DATE',
|
|
57
57
|
UNSUPPORTED: 'E_EVENT_LINE_UNSUPPORTED',
|
|
58
|
+
ERA_DATE_ORDER: 'E_EVENT_LINE_ERA_DATE_ORDER',
|
|
58
59
|
} as const;
|
|
59
60
|
|
|
60
61
|
/** A non-ISO date attempt: leading digits with a slash or dot separator. */
|
|
@@ -113,6 +114,11 @@ export function parseEventLine(
|
|
|
113
114
|
let currentTagGroup: Writable<TagGroup> | null = null;
|
|
114
115
|
let currentEvent: Writable<EventLineEvent> | null = null;
|
|
115
116
|
let currentEra: string | null = null;
|
|
117
|
+
// Indent of the open era's `[Name]` bracket; events must be indented deeper
|
|
118
|
+
// to belong to it. -1 ⇒ no era open (an indent-0 event is then era-less).
|
|
119
|
+
let eraIndent = -1;
|
|
120
|
+
// Indent of the current event; a line indented deeper is its description.
|
|
121
|
+
let currentEventIndent = 0;
|
|
116
122
|
const aliasMap = new Map<string, string>();
|
|
117
123
|
|
|
118
124
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -261,7 +267,9 @@ export function parseEventLine(
|
|
|
261
267
|
const cm = rest.match(ERA_COLLAPSED_RE);
|
|
262
268
|
if (cm) {
|
|
263
269
|
collapsed = cm[1]!.toLowerCase() === 'true';
|
|
264
|
-
rest = (
|
|
270
|
+
rest = (
|
|
271
|
+
rest.slice(0, cm.index) + rest.slice(cm.index! + cm[0].length)
|
|
272
|
+
).trim();
|
|
265
273
|
}
|
|
266
274
|
let color: string | null = null;
|
|
267
275
|
if (rest) {
|
|
@@ -323,9 +331,91 @@ export function parseEventLine(
|
|
|
323
331
|
);
|
|
324
332
|
}
|
|
325
333
|
|
|
334
|
+
// Eras render as date-spanning brackets, so an event dated outside its era's
|
|
335
|
+
// chronological position makes adjacent era brackets overlap (§28.6a). Only
|
|
336
|
+
// meaningful when the date scale drives x-position.
|
|
337
|
+
if (options.scale) {
|
|
338
|
+
validateEraDateOrder(result.events, result.eras, pushWarning);
|
|
339
|
+
}
|
|
340
|
+
|
|
326
341
|
return result;
|
|
327
342
|
}
|
|
328
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Warn when a dated event sits in an era whose chronological run it breaks —
|
|
346
|
+
* i.e. it is dated after a later era begins, or before an earlier era ends.
|
|
347
|
+
* Eras are left-to-right date bands; a straggler makes their brackets overlap.
|
|
348
|
+
*/
|
|
349
|
+
function validateEraDateOrder(
|
|
350
|
+
events: readonly EventLineEvent[],
|
|
351
|
+
eras: readonly EventLineEra[],
|
|
352
|
+
pushWarning: (line: number, message: string, code?: string) => void
|
|
353
|
+
): void {
|
|
354
|
+
if (eras.length < 2) return;
|
|
355
|
+
const order = new Map(eras.map((e, i) => [e.name, i]));
|
|
356
|
+
const dated = events.filter(
|
|
357
|
+
(e) => e.dateValue !== null && e.era !== null && order.has(e.era)
|
|
358
|
+
);
|
|
359
|
+
if (dated.length === 0) return;
|
|
360
|
+
|
|
361
|
+
// Earliest and latest dated event per era index.
|
|
362
|
+
const perEra = new Map<
|
|
363
|
+
number,
|
|
364
|
+
{ min: EventLineEvent; max: EventLineEvent }
|
|
365
|
+
>();
|
|
366
|
+
for (const ev of dated) {
|
|
367
|
+
const idx = order.get(ev.era!)!;
|
|
368
|
+
const cur = perEra.get(idx);
|
|
369
|
+
if (!cur) {
|
|
370
|
+
perEra.set(idx, { min: ev, max: ev });
|
|
371
|
+
} else {
|
|
372
|
+
if (ev.dateValue! < cur.min.dateValue!) cur.min = ev;
|
|
373
|
+
if (ev.dateValue! > cur.max.dateValue!) cur.max = ev;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const n = eras.length;
|
|
378
|
+
// prefixMax[k] = the latest-dated event across eras with index < k.
|
|
379
|
+
// suffixMin[k] = the earliest-dated event across eras with index > k.
|
|
380
|
+
const prefixMax: (EventLineEvent | null)[] = new Array(n).fill(null);
|
|
381
|
+
const suffixMin: (EventLineEvent | null)[] = new Array(n).fill(null);
|
|
382
|
+
for (let k = 1; k < n; k++) {
|
|
383
|
+
const prev = perEra.get(k - 1)?.max ?? null;
|
|
384
|
+
const carried = prefixMax[k - 1] ?? null;
|
|
385
|
+
prefixMax[k] =
|
|
386
|
+
prev && (!carried || prev.dateValue! > carried.dateValue!)
|
|
387
|
+
? prev
|
|
388
|
+
: carried;
|
|
389
|
+
}
|
|
390
|
+
for (let k = n - 2; k >= 0; k--) {
|
|
391
|
+
const next = perEra.get(k + 1)?.min ?? null;
|
|
392
|
+
const carried = suffixMin[k + 1] ?? null;
|
|
393
|
+
suffixMin[k] =
|
|
394
|
+
next && (!carried || next.dateValue! < carried.dateValue!)
|
|
395
|
+
? next
|
|
396
|
+
: carried;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
for (const ev of dated) {
|
|
400
|
+
const k = order.get(ev.era!)!;
|
|
401
|
+
const ahead = suffixMin[k];
|
|
402
|
+
const behind = prefixMax[k];
|
|
403
|
+
if (ahead && ev.dateValue! > ahead.dateValue!) {
|
|
404
|
+
pushWarning(
|
|
405
|
+
ev.lineNumber,
|
|
406
|
+
`"${ev.label}" (${ev.date}) is in era "${ev.era}" but dated after era "${ahead.era}" begins (${ahead.date}). event-line eras run left-to-right by date — fix the date or move it to the right era, or their brackets will overlap.`,
|
|
407
|
+
EVENT_LINE_DIAGNOSTIC_CODES.ERA_DATE_ORDER
|
|
408
|
+
);
|
|
409
|
+
} else if (behind && ev.dateValue! < behind.dateValue!) {
|
|
410
|
+
pushWarning(
|
|
411
|
+
ev.lineNumber,
|
|
412
|
+
`"${ev.label}" (${ev.date}) is in era "${ev.era}" but dated before era "${behind.era}" ends (${behind.date}). event-line eras run left-to-right by date — fix the date or move it to the right era, or their brackets will overlap.`,
|
|
413
|
+
EVENT_LINE_DIAGNOSTIC_CODES.ERA_DATE_ORDER
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
329
419
|
function parseEventHeader(
|
|
330
420
|
trimmed: string,
|
|
331
421
|
lineNumber: number,
|