@asciidoctor/core 3.0.4 → 4.0.0-alpha.1

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.
Files changed (91) hide show
  1. package/README.md +42 -10
  2. package/build/browser/index.js +24154 -0
  3. package/build/node/index.cjs +24735 -0
  4. package/{dist/css/asciidoctor.css → data/asciidoctor-default.css} +54 -53
  5. package/package.json +53 -100
  6. package/src/abstract_block.js +849 -0
  7. package/src/abstract_node.js +954 -0
  8. package/src/attribute_entry.js +12 -0
  9. package/src/attribute_list.js +380 -0
  10. package/src/block.js +168 -0
  11. package/src/browser/asset.js +22 -0
  12. package/src/browser/reader.js +138 -0
  13. package/src/browser.js +121 -0
  14. package/src/callouts.js +85 -0
  15. package/src/compliance.js +54 -0
  16. package/src/constants.js +665 -0
  17. package/src/convert.js +370 -0
  18. package/src/converter/composite.js +83 -0
  19. package/src/converter/docbook5.js +1031 -0
  20. package/src/converter/html5.js +1899 -0
  21. package/src/converter/manpage.js +935 -0
  22. package/src/converter/template.js +459 -0
  23. package/src/converter.js +478 -0
  24. package/src/data/stylesheet-data.js +2 -0
  25. package/src/document.js +2134 -0
  26. package/src/extensions.js +1952 -0
  27. package/src/footnote.js +28 -0
  28. package/src/helpers.js +355 -0
  29. package/src/index.js +138 -0
  30. package/src/inline.js +158 -0
  31. package/src/list.js +240 -0
  32. package/src/load.js +276 -0
  33. package/src/logging.js +526 -0
  34. package/src/parser.js +3661 -0
  35. package/src/path_resolver.js +472 -0
  36. package/src/reader.js +1755 -0
  37. package/src/rx.js +829 -0
  38. package/src/section.js +354 -0
  39. package/src/stylesheets.js +30 -0
  40. package/src/substitutors.js +2241 -0
  41. package/src/syntaxHighlighter/highlightjs.js +90 -0
  42. package/src/syntaxHighlighter/html_pipeline.js +33 -0
  43. package/src/syntax_highlighter.js +304 -0
  44. package/src/table.js +952 -0
  45. package/src/timings.js +78 -0
  46. package/types/abstract_block.d.ts +343 -0
  47. package/types/abstract_node.d.ts +471 -0
  48. package/types/attribute_entry.d.ts +7 -0
  49. package/types/attribute_list.d.ts +52 -0
  50. package/types/block.d.ts +55 -0
  51. package/types/browser/asset.d.ts +7 -0
  52. package/types/browser/reader.d.ts +29 -0
  53. package/types/callouts.d.ts +36 -0
  54. package/types/compliance.d.ts +23 -0
  55. package/types/constants.d.ts +268 -0
  56. package/types/convert.d.ts +34 -0
  57. package/types/converter/composite.d.ts +20 -0
  58. package/types/converter/docbook5.d.ts +41 -0
  59. package/types/converter/html5.d.ts +51 -0
  60. package/types/converter/manpage.d.ts +59 -0
  61. package/types/converter/template.d.ts +83 -0
  62. package/types/converter.d.ts +150 -0
  63. package/types/data/stylesheet-data.d.ts +2 -0
  64. package/types/document.d.ts +495 -0
  65. package/types/extensions.d.ts +876 -0
  66. package/types/footnote.d.ts +18 -0
  67. package/types/helpers.d.ts +146 -0
  68. package/types/index.d.ts +73 -3731
  69. package/types/inline.d.ts +69 -0
  70. package/types/list.d.ts +114 -0
  71. package/types/load.d.ts +39 -0
  72. package/types/logging.d.ts +187 -0
  73. package/types/parser.d.ts +114 -0
  74. package/types/path_resolver.d.ts +103 -0
  75. package/types/reader.d.ts +184 -0
  76. package/types/rx.d.ts +513 -0
  77. package/types/section.d.ts +122 -0
  78. package/types/stylesheets.d.ts +10 -0
  79. package/types/substitutors.d.ts +208 -0
  80. package/types/syntaxHighlighter/highlightjs.d.ts +33 -0
  81. package/types/syntaxHighlighter/html_pipeline.d.ts +16 -0
  82. package/types/syntax_highlighter.d.ts +167 -0
  83. package/types/table.d.ts +231 -0
  84. package/types/timings.d.ts +25 -0
  85. package/types/tsconfig.json +9 -0
  86. package/LICENSE +0 -21
  87. package/dist/browser/asciidoctor.js +0 -47654
  88. package/dist/browser/asciidoctor.min.js +0 -1452
  89. package/dist/graalvm/asciidoctor.js +0 -47402
  90. package/dist/node/asciidoctor.cjs +0 -21567
  91. package/dist/node/asciidoctor.js +0 -23037
@@ -0,0 +1,665 @@
1
+ // ESM conversion of lib/asciidoctor.rb
2
+ //
3
+ // Defines all module-level constants and re-exports every regex constant from
4
+ // rx.js so that other modules can import everything from this single file.
5
+ //
6
+ // Omissions vs. the Ruby source
7
+ // - Ruby-encoding constants (UTF_8, BOM_BYTES_*) – JS strings are always UTF-16.
8
+ // - File-mode strings (FILE_READ_MODE, …) – Ruby open(2) semantics, no JS equivalent.
9
+ // - ROOT_DIR / LIB_DIR / DATA_DIR / USER_HOME – computed via import.meta.url below
10
+ // for Node.js; silently empty in environments where the URL API is unavailable.
11
+ // - const_missing / autoload – Ruby metaprogramming, not applicable in JS.
12
+ // - Compliance – defined in ./compliance.js (imported separately by substitutors.js).
13
+ // - RUBY_ENGINE / RUBY_ENGINE_OPAL – not applicable in JS.
14
+
15
+ // ── Re-export everything from rx.js ──────────────────────────────────────────
16
+ // Consumers (e.g. substitutors.js) import Rx constants through this file so
17
+ // they do not need a direct dependency on rx.js.
18
+ export * from './rx.js'
19
+
20
+ import {
21
+ CC_ALL,
22
+ CC_WORD,
23
+ CG_WORD,
24
+ CG_ALNUM,
25
+ CG_ALPHA,
26
+ QuoteAttributeListRxt,
27
+ } from './rx.js'
28
+
29
+ // Local helper – same as the one inside rx.js (not exported there).
30
+ const ru = (src, flags = '') => new RegExp(src, `u${flags}`)
31
+
32
+ // ── SafeMode ─────────────────────────────────────────────────────────────────
33
+ // Mirrors the Asciidoctor::SafeMode Ruby module.
34
+ const _safeModeNamesByValue = {
35
+ 0: 'unsafe',
36
+ 1: 'safe',
37
+ 10: 'server',
38
+ 20: 'secure',
39
+ }
40
+
41
+ export const SafeMode = {
42
+ /**
43
+ * A safe mode level that disables any of the security features enforced
44
+ * by Asciidoctor (Node is still subject to its own restrictions).
45
+ */
46
+ UNSAFE: 0,
47
+ /**
48
+ * A safe mode level that closely parallels safe mode in AsciiDoc. This value
49
+ * prevents access to files which reside outside the parent directory of
50
+ * the source file and disables any macro other than the `include::[]` directive.
51
+ */
52
+ SAFE: 1,
53
+ /**
54
+ * A safe mode level that disallows the document from setting attributes
55
+ * that would affect the conversion of the document, in addition to all the
56
+ * security features of {@link SafeMode.SAFE}. For instance, this level forbids
57
+ * changing the backend or source-highlighter using an attribute defined
58
+ * in the source document header. This is the most fundamental level of
59
+ * security for server deployments (hence the name).
60
+ */
61
+ SERVER: 10,
62
+ /**
63
+ * A safe mode level that disallows the document from attempting to read
64
+ * files from the file system and including the contents of them into the
65
+ * document, in additional to all the security features of {@link SafeMode.SERVER}.
66
+ * For instance, this level disallows use of the `include::[]` directive and the
67
+ * embedding of binary content (data uri), stylesheets and JavaScripts
68
+ * referenced by the document. (Asciidoctor and trusted extensions may still
69
+ * be allowed to embed trusted content into the document).
70
+ *
71
+ * Since Asciidoctor is aiming for wide adoption, this level is the default
72
+ * and is recommended for server deployments.
73
+ */
74
+ SECURE: 20,
75
+
76
+ /**
77
+ * Returns the numeric value for a safe-mode name string, or undefined.
78
+ * @param {string} name
79
+ * @returns {number|undefined}
80
+ */
81
+ valueForName(name) {
82
+ const key = String(name).toUpperCase()
83
+ const v = SafeMode[key]
84
+ return typeof v === 'number' ? v : undefined
85
+ },
86
+
87
+ /**
88
+ * @param {string} name
89
+ * @returns {number|undefined}
90
+ */
91
+ getValueForName(name) {
92
+ return this.valueForName(name)
93
+ },
94
+
95
+ /**
96
+ * Returns the lowercase name for a numeric safe-mode value, or undefined.
97
+ * @param {number} value
98
+ * @returns {string|undefined}
99
+ */
100
+ nameForValue(value) {
101
+ return _safeModeNamesByValue[value]
102
+ },
103
+
104
+ /**
105
+ * @param {number} value
106
+ * @returns {string|undefined}
107
+ */
108
+ getNameForValue(value) {
109
+ return this.nameForValue(value)
110
+ },
111
+
112
+ /**
113
+ * Returns all safe-mode names in ascending value order.
114
+ * @returns {string[]}
115
+ */
116
+ names() {
117
+ return Object.values(_safeModeNamesByValue)
118
+ },
119
+
120
+ /**
121
+ * @returns {string[]}
122
+ */
123
+ getNames() {
124
+ return this.names()
125
+ },
126
+ }
127
+
128
+ /**
129
+ * Named constants for the `contentModel` property on {@link AbstractBlock}.
130
+ *
131
+ * The content model controls what kind of content a block accepts and how it
132
+ * is converted.
133
+ *
134
+ * @example
135
+ * import { ContentModel } from '@asciidoctor/core'
136
+ * const verbatimBlocks = doc.findBy({}, (b) => b.contentModel === ContentModel.VERBATIM || 'reject')
137
+ */
138
+ export const ContentModel = {
139
+ /** The block contains other blocks (sections, sidebars, admonitions, …). */
140
+ COMPOUND: 'compound',
141
+ /** The block holds a paragraph of prose that receives normal substitutions. */
142
+ SIMPLE: 'simple',
143
+ /** The block holds verbatim text displayed as-is with verbatim substitutions (listing, literal). */
144
+ VERBATIM: 'verbatim',
145
+ /** The block holds unprocessed content passed directly to output with no substitutions (pass). */
146
+ RAW: 'raw',
147
+ /** The block has no content (e.g. image, thematic break). */
148
+ EMPTY: 'empty',
149
+ }
150
+
151
+ // ── File-system paths (Node.js only) ─────────────────────────────────────────
152
+ // In a browser / Deno / Opal-compiled context these will be empty strings.
153
+ let ROOT_DIR = ''
154
+ let LIB_DIR = ''
155
+ let DATA_DIR = ''
156
+ let USER_HOME = ''
157
+ try {
158
+ LIB_DIR = new URL('.', import.meta.url).pathname.replace(/\/$/, '')
159
+ ROOT_DIR = new URL('../../', import.meta.url).pathname.replace(/\/$/, '')
160
+ DATA_DIR = new URL('../../data', import.meta.url).pathname
161
+ // Prefer $HOME; fall back to $USERPROFILE (Windows) then process.cwd()
162
+ if (typeof process !== 'undefined') {
163
+ USER_HOME =
164
+ process.env.HOME ||
165
+ process.env.USERPROFILE ||
166
+ (process.cwd ? process.cwd() : '')
167
+ }
168
+ } catch {}
169
+ export { ROOT_DIR, LIB_DIR, DATA_DIR, USER_HOME }
170
+
171
+ // ── Primitive constants ───────────────────────────────────────────────────────
172
+ // The newline character used for output.
173
+ export const LF = '\n'
174
+
175
+ // The null character used as an internal separator for attribute values.
176
+ export const NULL = '\0'
177
+
178
+ // The tab character.
179
+ export const TAB = '\t'
180
+
181
+ // Maximum safe integer (= Number.MAX_SAFE_INTEGER).
182
+ export const MAX_INT = 9007199254740991
183
+
184
+ // ── Document defaults ─────────────────────────────────────────────────────────
185
+ export const DEFAULT_DOCTYPE = 'article'
186
+ export const DEFAULT_BACKEND = 'html5'
187
+
188
+ export const DEFAULT_STYLESHEET_KEYS = new Set(['', 'DEFAULT'])
189
+ export const DEFAULT_STYLESHEET_NAME = 'asciidoctor.css'
190
+
191
+ // Maps legacy backend aliases to the canonical backend name.
192
+ export const BACKEND_ALIASES = {
193
+ html: 'html5',
194
+ docbook: 'docbook5',
195
+ }
196
+
197
+ // Default page widths (points) used when computing absolute column widths.
198
+ export const DEFAULT_PAGE_WIDTHS = { docbook: 425 }
199
+
200
+ // Default output file extensions per base backend.
201
+ export const DEFAULT_EXTENSIONS = {
202
+ html: '.html',
203
+ docbook: '.xml',
204
+ pdf: '.pdf',
205
+ epub: '.epub',
206
+ manpage: '.man',
207
+ asciidoc: '.adoc',
208
+ }
209
+
210
+ // File extensions that are recognized as AsciiDoc documents.
211
+ // TODO: .txt should be deprecated
212
+ export const ASCIIDOC_EXTENSIONS = {
213
+ '.adoc': true,
214
+ '.asciidoc': true,
215
+ '.asc': true,
216
+ '.ad': true,
217
+ '.txt': true,
218
+ }
219
+
220
+ // ── Section titles ────────────────────────────────────────────────────────────
221
+ // Maps setext underline characters to section levels.
222
+ export const SETEXT_SECTION_LEVELS = {
223
+ '=': 0,
224
+ '-': 1,
225
+ '~': 2,
226
+ '^': 3,
227
+ '+': 4,
228
+ }
229
+
230
+ // ── Admonition ───────────────────────────────────────────────────────────────
231
+ export const ADMONITION_STYLES = new Set([
232
+ 'NOTE',
233
+ 'TIP',
234
+ 'IMPORTANT',
235
+ 'WARNING',
236
+ 'CAUTION',
237
+ ])
238
+ export const ADMONITION_STYLE_HEADS = new Set(
239
+ [...ADMONITION_STYLES].map((s) => s[0])
240
+ )
241
+
242
+ // ── Block styles ──────────────────────────────────────────────────────────────
243
+ export const PARAGRAPH_STYLES = new Set([
244
+ 'comment',
245
+ 'example',
246
+ 'literal',
247
+ 'listing',
248
+ 'normal',
249
+ 'open',
250
+ 'pass',
251
+ 'quote',
252
+ 'sidebar',
253
+ 'source',
254
+ 'verse',
255
+ 'abstract',
256
+ 'partintro',
257
+ ])
258
+
259
+ export const VERBATIM_STYLES = new Set([
260
+ 'literal',
261
+ 'listing',
262
+ 'source',
263
+ 'verse',
264
+ ])
265
+
266
+ // ── Delimited blocks ──────────────────────────────────────────────────────────
267
+ // Maps delimiter string → [context, Set of alternative styles].
268
+ // Ruby symbols are represented as plain strings.
269
+ export const DELIMITED_BLOCKS = {
270
+ '--': [
271
+ 'open',
272
+ new Set([
273
+ 'comment',
274
+ 'example',
275
+ 'literal',
276
+ 'listing',
277
+ 'pass',
278
+ 'quote',
279
+ 'sidebar',
280
+ 'source',
281
+ 'verse',
282
+ 'admonition',
283
+ 'abstract',
284
+ 'partintro',
285
+ ]),
286
+ ],
287
+ '----': ['listing', new Set(['literal', 'source'])],
288
+ '....': ['literal', new Set(['listing', 'source'])],
289
+ '====': ['example', new Set(['admonition'])],
290
+ '****': ['sidebar', new Set()],
291
+ ____: ['quote', new Set(['verse'])],
292
+ '++++': ['pass', new Set(['stem', 'latexmath', 'asciimath'])],
293
+ '|===': ['table', new Set()],
294
+ ',===': ['table', new Set()],
295
+ ':===': ['table', new Set()],
296
+ '!===': ['table', new Set()],
297
+ '~~~~': ['open', new Set(['abstract', 'partintro'])],
298
+ '////': ['comment', new Set()],
299
+ '```': ['fenced_code', new Set()],
300
+ }
301
+
302
+ // First 2 characters of each delimiter → true (used for fast sniff).
303
+ export const DELIMITED_BLOCK_HEADS = Object.fromEntries(
304
+ Object.keys(DELIMITED_BLOCKS).map((k) => [k.slice(0, 2), true])
305
+ )
306
+
307
+ // 4-character delimiters only: delimiter → last character (used for tail matching).
308
+ export const DELIMITED_BLOCK_TAILS = Object.fromEntries(
309
+ Object.keys(DELIMITED_BLOCKS)
310
+ .filter((k) => k.length === 4)
311
+ .map((k) => [k, k[k.length - 1]])
312
+ )
313
+
314
+ // ── Captions ──────────────────────────────────────────────────────────────────
315
+ // Maps block context to the document attribute that holds its caption prefix.
316
+ // NOTE: 'figure' key is a string for historical reasons (used by image blocks).
317
+ export const CAPTION_ATTRIBUTE_NAMES = {
318
+ example: 'example-caption',
319
+ figure: 'figure-caption',
320
+ listing: 'listing-caption',
321
+ table: 'table-caption',
322
+ }
323
+
324
+ // ── Layout breaks ─────────────────────────────────────────────────────────────
325
+ export const LAYOUT_BREAK_CHARS = {
326
+ "'": 'thematic_break',
327
+ '<': 'page_break',
328
+ }
329
+
330
+ export const MARKDOWN_THEMATIC_BREAK_CHARS = {
331
+ '-': 'thematic_break',
332
+ '*': 'thematic_break',
333
+ _: 'thematic_break',
334
+ }
335
+
336
+ export const HYBRID_LAYOUT_BREAK_CHARS = {
337
+ ...LAYOUT_BREAK_CHARS,
338
+ ...MARKDOWN_THEMATIC_BREAK_CHARS,
339
+ }
340
+
341
+ // ── Lists ─────────────────────────────────────────────────────────────────────
342
+ export const NESTABLE_LIST_CONTEXTS = ['ulist', 'olist', 'dlist']
343
+
344
+ // Ordered list style names, in selection priority order.
345
+ export const ORDERED_LIST_STYLES = [
346
+ 'arabic',
347
+ 'loweralpha',
348
+ 'lowerroman',
349
+ 'upperalpha',
350
+ 'upperroman',
351
+ ]
352
+
353
+ // Maps an ordered list style name to its CSS list-style-type keyword.
354
+ export const ORDERED_LIST_KEYWORDS = {
355
+ loweralpha: 'a',
356
+ lowerroman: 'i',
357
+ upperalpha: 'A',
358
+ upperroman: 'I',
359
+ }
360
+
361
+ // ── Inline markers ────────────────────────────────────────────────────────────
362
+ export const ATTR_REF_HEAD = '{'
363
+ export const LIST_CONTINUATION = '+'
364
+ // NOTE AsciiDoc.py allows + to be preceded by TAB; Asciidoctor does not
365
+ export const HARD_LINE_BREAK = ' +'
366
+ export const LINE_CONTINUATION = ' \\'
367
+ export const LINE_CONTINUATION_LEGACY = ' +'
368
+
369
+ // ── Math / STEM ───────────────────────────────────────────────────────────────
370
+ export const BLOCK_MATH_DELIMITERS = {
371
+ asciimath: ['\\$', '\\$'],
372
+ latexmath: ['\\[', '\\]'],
373
+ }
374
+
375
+ export const INLINE_MATH_DELIMITERS = {
376
+ asciimath: ['\\$', '\\$'],
377
+ latexmath: ['\\(', '\\)'],
378
+ }
379
+
380
+ // Maps STEM type aliases to canonical type names.
381
+ // Accessing an unknown key returns 'asciimath' (mirrors Ruby Hash#default).
382
+ export const STEM_TYPE_ALIASES = new Proxy(
383
+ { latexmath: 'latexmath', latex: 'latexmath', tex: 'latexmath' },
384
+ {
385
+ get: (target, key) =>
386
+ Object.hasOwn(target, key) ? target[key] : 'asciimath',
387
+ }
388
+ )
389
+
390
+ // ── Third-party library versions ──────────────────────────────────────────────
391
+ export const FONT_AWESOME_VERSION = '4.7.0'
392
+ export const HIGHLIGHT_JS_VERSION = '9.18.3'
393
+ export const MATHJAX_VERSION = '2.7.9'
394
+
395
+ // ── Default document attributes ───────────────────────────────────────────────
396
+ export const DEFAULT_ATTRIBUTES = {
397
+ 'appendix-caption': 'Appendix',
398
+ 'appendix-refsig': 'Appendix',
399
+ 'caution-caption': 'Caution',
400
+ 'chapter-refsig': 'Chapter',
401
+ 'example-caption': 'Example',
402
+ 'figure-caption': 'Figure',
403
+ 'important-caption': 'Important',
404
+ 'last-update-label': 'Last updated',
405
+ 'note-caption': 'Note',
406
+ 'part-refsig': 'Part',
407
+ prewrap: '',
408
+ sectids: '',
409
+ 'section-refsig': 'Section',
410
+ 'table-caption': 'Table',
411
+ 'tip-caption': 'Tip',
412
+ 'toc-placement': 'auto',
413
+ 'toc-title': 'Table of Contents',
414
+ 'untitled-label': 'Untitled',
415
+ 'version-label': 'Version',
416
+ 'warning-caption': 'Warning',
417
+ }
418
+
419
+ // Attributes that may be changed mid-document (e.g. sectnums toggling).
420
+ export const FLEXIBLE_ATTRIBUTES = ['sectnums']
421
+
422
+ // Predefined (intrinsic) attribute substitutions.
423
+ export const INTRINSIC_ATTRIBUTES = {
424
+ startsb: '[',
425
+ endsb: ']',
426
+ vbar: '|',
427
+ caret: '^',
428
+ asterisk: '*',
429
+ tilde: '~',
430
+ plus: '&#43;',
431
+ backslash: '\\',
432
+ backtick: '`',
433
+ blank: '',
434
+ empty: '',
435
+ sp: ' ',
436
+ 'two-colons': '::',
437
+ 'two-semicolons': ';;',
438
+ nbsp: '&#160;',
439
+ deg: '&#176;',
440
+ zwsp: '&#8203;',
441
+ quot: '&#34;',
442
+ apos: '&#39;',
443
+ lsquo: '&#8216;',
444
+ rsquo: '&#8217;',
445
+ ldquo: '&#8220;',
446
+ rdquo: '&#8221;',
447
+ wj: '&#8288;',
448
+ brvbar: '&#166;',
449
+ pp: '&#43;&#43;',
450
+ cpp: 'C&#43;&#43;',
451
+ cxx: 'C&#43;&#43;',
452
+ amp: '&',
453
+ lt: '<',
454
+ gt: '>',
455
+ }
456
+
457
+ // ── Quote substitutions ───────────────────────────────────────────────────────
458
+ // Each entry is a triple: [type, scope, RegExp].
459
+ // type – string matching a Ruby symbol (e.g. 'strong', 'emphasis', …)
460
+ // scope – 'unconstrained' | 'constrained'
461
+ //
462
+ // Ruby regex flag notes
463
+ // /m in Ruby = dotAll (. matches \n); handled by CC_ALL = '[\\s\\S]' → no 's' flag needed.
464
+ // ^ / $ are always line anchors in Ruby → need JS 'm' flag when ^ or $ appears.
465
+ // \p{…} Unicode properties require JS 'u' flag (provided by the ru() helper).
466
+ //
467
+ // Backtick character (U+0060) cannot appear literally inside a JS template literal,
468
+ // so it is injected via the BT variable in template expressions.
469
+ const BT = '\x60' // U+0060 GRAVE ACCENT / backtick
470
+
471
+ const _normalQuoteSubs = [
472
+ // **strong**
473
+ [
474
+ 'strong',
475
+ 'unconstrained',
476
+ ru(String.raw`\\?(?:${QuoteAttributeListRxt})?\*\*(${CC_ALL}+?)\*\*`),
477
+ ],
478
+ // *strong*
479
+ [
480
+ 'strong',
481
+ 'constrained',
482
+ ru(
483
+ String.raw`(^|[^${CC_WORD};:}])(?:${QuoteAttributeListRxt})?\*(\S|\S${CC_ALL}*?\S)\*(?!${CG_WORD})`,
484
+ 'm'
485
+ ),
486
+ ],
487
+ // "`double-quoted`"
488
+ [
489
+ 'double',
490
+ 'constrained',
491
+ ru(
492
+ String.raw`(^|[^${CC_WORD};:}])(?:${QuoteAttributeListRxt})?"${BT}(\S|\S${CC_ALL}*?\S)${BT}"(?!${CG_WORD})`,
493
+ 'm'
494
+ ),
495
+ ],
496
+ // '`single-quoted`'
497
+ [
498
+ 'single',
499
+ 'constrained',
500
+ ru(
501
+ String.raw`(^|[^${CC_WORD};:${BT}}])(?:${QuoteAttributeListRxt})?'${BT}(\S|\S${CC_ALL}*?\S)${BT}'(?!${CG_WORD})`,
502
+ 'm'
503
+ ),
504
+ ],
505
+ // ``monospaced``
506
+ [
507
+ 'monospaced',
508
+ 'unconstrained',
509
+ ru(
510
+ String.raw`\\?(?:${QuoteAttributeListRxt})?${BT}${BT}(${CC_ALL}+?)${BT}${BT}`
511
+ ),
512
+ ],
513
+ // `monospaced`
514
+ [
515
+ 'monospaced',
516
+ 'constrained',
517
+ ru(
518
+ String.raw`(^|[^${CC_WORD};:"'${BT}}])(?:${QuoteAttributeListRxt})?${BT}(\S|\S${CC_ALL}*?\S)${BT}(?![${CC_WORD}"'${BT}])`,
519
+ 'm'
520
+ ),
521
+ ],
522
+ // __emphasis__
523
+ [
524
+ 'emphasis',
525
+ 'unconstrained',
526
+ ru(String.raw`\\?(?:${QuoteAttributeListRxt})?__(${CC_ALL}+?)__`),
527
+ ],
528
+ // _emphasis_
529
+ [
530
+ 'emphasis',
531
+ 'constrained',
532
+ ru(
533
+ String.raw`(^|[^${CC_WORD};:}])(?:${QuoteAttributeListRxt})?_(\S|\S${CC_ALL}*?\S)_(?!${CG_WORD})`,
534
+ 'm'
535
+ ),
536
+ ],
537
+ // ##mark##
538
+ [
539
+ 'mark',
540
+ 'unconstrained',
541
+ ru(String.raw`\\?(?:${QuoteAttributeListRxt})?##(${CC_ALL}+?)##`),
542
+ ],
543
+ // #mark#
544
+ [
545
+ 'mark',
546
+ 'constrained',
547
+ ru(
548
+ String.raw`(^|[^${CC_WORD}&;:}])(?:${QuoteAttributeListRxt})?#(\S|\S${CC_ALL}*?\S)#(?!${CG_WORD})`,
549
+ 'm'
550
+ ),
551
+ ],
552
+ // ^superscript^
553
+ [
554
+ 'superscript',
555
+ 'unconstrained',
556
+ ru(String.raw`\\?(?:${QuoteAttributeListRxt})?\^(\S+?)\^`),
557
+ ],
558
+ // ~subscript~
559
+ [
560
+ 'subscript',
561
+ 'unconstrained',
562
+ ru(String.raw`\\?(?:${QuoteAttributeListRxt})?~(\S+?)~`),
563
+ ],
564
+ ]
565
+
566
+ // Compatibility mode overrides (entries replaced / inserted relative to normal).
567
+ const _compatQuoteSubs = [..._normalQuoteSubs]
568
+ // ``quoted''
569
+ _compatQuoteSubs[2] = [
570
+ 'double',
571
+ 'constrained',
572
+ ru(
573
+ String.raw`(^|[^${CC_WORD};:}])(?:${QuoteAttributeListRxt})?${BT}${BT}(\S|\S${CC_ALL}*?\S)''(?!${CG_WORD})`,
574
+ 'm'
575
+ ),
576
+ ]
577
+ // `quoted'
578
+ _compatQuoteSubs[3] = [
579
+ 'single',
580
+ 'constrained',
581
+ ru(
582
+ String.raw`(^|[^${CC_WORD};:}])(?:${QuoteAttributeListRxt})?${BT}(\S|\S${CC_ALL}*?\S)'(?!${CG_WORD})`,
583
+ 'm'
584
+ ),
585
+ ]
586
+ // ++monospaced++
587
+ _compatQuoteSubs[4] = [
588
+ 'monospaced',
589
+ 'unconstrained',
590
+ ru(String.raw`\\?(?:${QuoteAttributeListRxt})?\+\+(${CC_ALL}+?)\+\+`),
591
+ ]
592
+ // +monospaced+
593
+ _compatQuoteSubs[5] = [
594
+ 'monospaced',
595
+ 'constrained',
596
+ ru(
597
+ String.raw`(^|[^${CC_WORD};:}])(?:${QuoteAttributeListRxt})?\+(\S|\S${CC_ALL}*?\S)\+(?!${CG_WORD})`,
598
+ 'm'
599
+ ),
600
+ ]
601
+ // 'emphasis' – inserted before original index 3 (single-quoted)
602
+ _compatQuoteSubs.splice(3, 0, [
603
+ 'emphasis',
604
+ 'constrained',
605
+ ru(
606
+ String.raw`(^|[^${CC_WORD};:}])(?:${QuoteAttributeListRxt})?'(\S|\S${CC_ALL}*?\S)'(?!${CG_WORD})`,
607
+ 'm'
608
+ ),
609
+ ])
610
+
611
+ // Keyed by boolean compat mode (false = normal, true = compat).
612
+ // JS object keys are always strings, so QUOTE_SUBS[false] coerces to QUOTE_SUBS['false'].
613
+ export const QUOTE_SUBS = { false: _normalQuoteSubs, true: _compatQuoteSubs }
614
+
615
+ // ── Text replacements ─────────────────────────────────────────────────────────
616
+ // Each entry is a triple: [RegExp, replacement String, position hint].
617
+ // position hints: 'none' | 'leading' | 'bounding'
618
+ //
619
+ // NOTE: order of replacements is significant.
620
+ export const REPLACEMENTS = [
621
+ // (C)
622
+ [/\\?\(C\)/, '&#169;', 'none'],
623
+ // (R)
624
+ [/\\?\(R\)/, '&#174;', 'none'],
625
+ // (TM)
626
+ [/\\?\(TM\)/, '&#8482;', 'none'],
627
+ // foo -- bar (either space may be a newline; ^ / $ are line anchors → 'm' flag)
628
+ [/(?: |\n|^|\\)--(?: |\n|$)/m, '&#8201;&#8212;&#8201;', 'none'],
629
+ // foo--bar
630
+ [
631
+ ru(String.raw`(${CG_WORD})\\?--(?=${CG_WORD})`),
632
+ '&#8212;&#8203;',
633
+ 'leading',
634
+ ],
635
+ // ellipsis
636
+ [/\\?\.\.\./, '&#8230;&#8203;', 'none'],
637
+ // right single quote
638
+ [/\\?`'/, '&#8217;', 'none'],
639
+ // apostrophe (inside a word)
640
+ [ru(String.raw`(${CG_ALNUM})\\?'(?=${CG_ALPHA})`), '&#8217;', 'leading'],
641
+ // right arrow ->
642
+ [/\\?-&gt;/, '&#8594;', 'none'],
643
+ // right double arrow =>
644
+ [/\\?=&gt;/, '&#8658;', 'none'],
645
+ // left arrow <-
646
+ [/\\?&lt;-/, '&#8592;', 'none'],
647
+ // left double arrow <=
648
+ [/\\?&lt;=/, '&#8656;', 'none'],
649
+ // restore entities
650
+ [
651
+ /\\?(&)amp;((?:[a-zA-Z][a-zA-Z]+\d{0,2}|#\d\d\d{0,4}|#x[\da-fA-F][\da-fA-F][\da-fA-F]{0,3});)/,
652
+ '',
653
+ 'bounding',
654
+ ],
655
+ ]
656
+
657
+ export const BASIC_SUBS = Object.freeze(['specialcharacters'])
658
+ export const NORMAL_SUBS = Object.freeze([
659
+ 'specialcharacters',
660
+ 'quotes',
661
+ 'attributes',
662
+ 'replacements',
663
+ 'macros',
664
+ 'post_replacements',
665
+ ])