rapier-html 1.2.52 → 1.2.55

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.
@@ -2,7 +2,7 @@
2
2
  import {markdownSourcePositions} from "./md-source.mjs";
3
3
 
4
4
  export function fontDataUrl(url) {
5
- if (typeof url !== 'string') return null;
5
+ if (typeof url !== 'string' || url.length < 28 || (url.charCodeAt(0) | 32) !== 0x64 || url.charCodeAt(4) !== 0x3a) return null;
6
6
  const match = /^data:font\/(woff2|woff|ttf|otf);base64,([A-Za-z0-9+/]+={0,2})$/i.exec(url);
7
7
  if (!match || match[2].length % 4) return null;
8
8
  const padding = match[2].endsWith('==') ? 2 : match[2].endsWith('=') ? 1 : 0;
@@ -25,6 +25,8 @@ const inkReader = () => {
25
25
  const INK_OPEN_BODY = '(' + INK_KINDS.join('|') + ')(?: ([1-9]\\d{0,5}))?(?: (' + COLOR_VALUE + '))?(?: w=([1-9]\\d?))?(?: box=(\\d{1,6}),(\\d{1,6}))?(?: at=(' + INK_INT + '),(' + INK_INT + '))?'
26
26
  + '(?: (' + INK_INT + ',' + INK_INT + '(?: ' + INK_INT + ',' + INK_INT + ')*))?';
27
27
  const INK_OPEN_PATTERN = '<!--ink ' + INK_OPEN_BODY + '-->';
28
+ const INK_OPEN_EXACT_RE = new RegExp('^' + INK_OPEN_PATTERN + '$');
29
+ const INK_OPEN_PREFIX_RE = new RegExp('^' + INK_OPEN_PATTERN);
28
30
 
29
31
  function readInkMatch(match) {
30
32
  const linked = match[1] === 'arrow' || match[1] === 'end';
@@ -53,17 +55,17 @@ const inkReader = () => {
53
55
  }
54
56
 
55
57
  function parseInkOpen(comment) {
56
- if (typeof comment !== 'string') return null;
57
- const match = new RegExp('^' + INK_OPEN_PATTERN + '$').exec(comment);
58
+ if (typeof comment !== 'string' || !comment.startsWith('<!--ink ')) return null;
59
+ const match = INK_OPEN_EXACT_RE.exec(comment);
58
60
  return match && readInkMatch(match);
59
61
  }
60
62
 
61
63
  function parseInkBody(body) {
62
64
  return typeof body === 'string' ? parseInkOpen('<!--ink ' + body + '-->') : null;
63
65
  }
64
- return { TEXT_COLOR_NAMES, COLOR_NAME_ALT, COLOR_VALUE, INK_KINDS, INK_PATH_MAX, INK_INT_MAX, INK_OPEN_PATTERN, readInkMatch, parseInkOpen, parseInkBody };
66
+ return { TEXT_COLOR_NAMES, COLOR_NAME_ALT, COLOR_VALUE, INK_KINDS, INK_PATH_MAX, INK_INT_MAX, INK_OPEN_PATTERN, INK_OPEN_EXACT_RE, INK_OPEN_PREFIX_RE, readInkMatch, parseInkOpen, parseInkBody };
65
67
  };
66
- const { TEXT_COLOR_NAMES, COLOR_VALUE, INK_KINDS, INK_PATH_MAX, INK_INT_MAX, INK_OPEN_PATTERN, readInkMatch, parseInkOpen, parseInkBody } = inkReader();
68
+ const { TEXT_COLOR_NAMES, COLOR_VALUE, INK_KINDS, INK_PATH_MAX, INK_INT_MAX, INK_OPEN_PATTERN, INK_OPEN_EXACT_RE, INK_OPEN_PREFIX_RE, readInkMatch, parseInkOpen, parseInkBody } = inkReader();
67
69
  export { readInkMatch, parseInkOpen, parseInkBody };
68
70
 
69
71
  const TEXT_COLOR_HEX_TO_NAME = Object.freeze(
@@ -262,7 +264,7 @@ export function createTypography({normalizeFontFamily, fontFamilyCss, FONT_STACK
262
264
  md.core.ruler.after('linkify', 'rapier-typography', state => {
263
265
  for (let at = 0; at < state.tokens.length; at++) {
264
266
  const block = state.tokens[at];
265
- if (block.type !== 'inline' || !block.children || block.content.indexOf('<') < 0) continue;
267
+ if (block.type !== 'inline' || !block.children || block.content.indexOf('<') < 0 || !/<span/i.test(block.content)) continue;
266
268
  const stack = []; let depth = 0, overflow = 0;
267
269
  for (const token of block.children) {
268
270
  if (token.type !== 'html_inline') { depth += token.nesting || 0; continue; }
@@ -297,7 +299,8 @@ export const typography = createTypography({normalizeFontFamily, fontFamilyCss,
297
299
  export function typographySourceRanges(source, parser) { return markdownSourcePositions(source, parser).typography; }
298
300
 
299
301
  const INK_CLOSE = '<!--/ink-->';
300
- export { INK_KINDS, INK_CLOSE, INK_PATH_MAX, INK_INT_MAX, INK_OPEN_PATTERN };
302
+ const INK_OPEN_PREFIX = '<!--ink ';
303
+ export { INK_KINDS, INK_CLOSE, INK_PATH_MAX, INK_INT_MAX, INK_OPEN_PATTERN, INK_OPEN_PREFIX };
301
304
 
302
305
  const inkInt = (value, name) => {
303
306
  if (!Number.isInteger(value) || Math.abs(value) > INK_INT_MAX) throw new TypeError('ink: ' + name + ' must be an integer within ' + INK_INT_MAX);
@@ -344,16 +347,16 @@ export function formatInkRun(mark, content) {
344
347
  }
345
348
 
346
349
  export function matchInkOpen(text) {
347
- if (typeof text !== 'string') return null;
348
- const match = new RegExp('^' + INK_OPEN_PATTERN).exec(text);
350
+ if (typeof text !== 'string' || !text.startsWith('<!--ink ')) return null;
351
+ const match = INK_OPEN_PREFIX_RE.exec(text);
349
352
  if (!match) return null;
350
353
  const mark = readInkMatch(match);
351
354
  return mark && { mark, length: match[0].length };
352
355
  }
353
356
 
354
357
  export function inkOpenBody(opener) {
355
- if (typeof opener !== 'string') return null;
356
- const match = new RegExp('^' + INK_OPEN_PATTERN + '$').exec(opener);
358
+ if (typeof opener !== 'string' || !opener.startsWith('<!--ink ')) return null;
359
+ const match = INK_OPEN_EXACT_RE.exec(opener);
357
360
  return match && readInkMatch(match) ? opener.slice(8, -3) : null;
358
361
  }
359
362
  export function formatInkOpenFromBody(body) {
@@ -736,5 +739,5 @@ export function isPageBreakLine(text) {
736
739
  }
737
740
 
738
741
  export function isPageBreakBlock(text) {
739
- return typeof text === 'string' && /^<!--md-break:v1 page-->\r?\n?$/.test(text);
742
+ return typeof text === 'string' && text.startsWith(PAGE_BREAK_MARKER) && (text.length === 23 || (text.length === 24 && (text.charCodeAt(23) === 0x0a || text.charCodeAt(23) === 0x0d)) || (text.length === 25 && text.charCodeAt(23) === 0x0d && text.charCodeAt(24) === 0x0a));
740
743
  }
@@ -2,7 +2,7 @@
2
2
  // Copyright (c) 2026 Jack Skipworth. See LICENSE-MIT.
3
3
  // Feather callout paths retain the copyright and MIT notice in agent/vendor/NOTICE.txt.
4
4
  import {tuneMarkdownEngine} from "./md-engine.mjs";
5
- import {fontDataUrl as rapierFontDataUrl} from "./md-marks.mjs";
5
+ import {fontDataUrl as rapierFontDataUrl, INK_OPEN_PREFIX, PAGE_BREAK_MARKER} from "./md-marks.mjs";
6
6
  import {typography as rapierTypography, matchInkOpen as rapierMatchInkOpen, isInkClose as rapierIsInkClose,
7
7
  inkOpenBody as rapierInkOpenBody, parseInkOpen as rapierParseInkOpen, isPageBreakBlock as rapierIsPageBreakBlock} from "./md-marks.mjs";
8
8
  import {installMathRules} from "./math/markdown-rules.mjs";
@@ -39,19 +39,66 @@ const RAPIER_MARKDOWN_SPEC = Object.freeze({
39
39
  rules: Object.freeze({ mathBlock: 'temml_math_block' }),
40
40
  });
41
41
 
42
- const _rapierLinkifyCandidate = {names: null, pattern: null};
43
- function _rapierLinkifyText(state) {
44
- const md = state.md;
45
- if (!md.options.linkify) return;
46
- const names = md.linkify.re.get_schema_names();
42
+ const _rapierLinkifyCandidate = {names: null, schemaRe: null, tldsRef: null, tldSet: null, maxTld: 6};
43
+ function _rapierLinkFlags(text, linkify) {
44
+ const len = text.length;
45
+ if (len < 4) return 0;
46
+ const names = linkify.re.get_schema_names();
47
47
  if (_rapierLinkifyCandidate.names !== names) {
48
48
  _rapierLinkifyCandidate.names = names;
49
- _rapierLinkifyCandidate.pattern = new RegExp(names.source + '|@|\\.[^ \\t\\n\\v\\f\\r]', 'i');
49
+ _rapierLinkifyCandidate.schemaRe = new RegExp(names.source, 'i');
50
+ }
51
+ let flags = 0;
52
+ if (text.indexOf(':') >= 0 || text.indexOf('/') >= 0 || text.indexOf('w') >= 0 || text.indexOf('W') >= 0) {
53
+ if (_rapierLinkifyCandidate.schemaRe.test(text)) flags |= 1;
50
54
  }
51
- const candidate = _rapierLinkifyCandidate.pattern;
55
+ const dotFirst = text.indexOf('.');
56
+ if (dotFirst < 0) return flags;
57
+ const atPos = text.indexOf('@');
58
+ if (atPos > 0 && text.indexOf('.', atPos + 2) > 0) flags |= 4;
59
+ if (!linkify.__opts__.fuzzyLink) return flags;
60
+ if (linkify.__opts__.fuzzyIP && /\d\.\d/.test(text)) return flags | 2;
61
+ const tlds = linkify.__opts__.tlds;
62
+ if (_rapierLinkifyCandidate.tldsRef !== tlds) {
63
+ _rapierLinkifyCandidate.tldsRef = tlds;
64
+ const set = new Set();
65
+ let max = 2;
66
+ for (let i = 0; i < tlds.length; i++) {
67
+ const t = tlds[i].toLowerCase();
68
+ set.add(t);
69
+ if (t.length > max) max = t.length;
70
+ }
71
+ _rapierLinkifyCandidate.tldSet = set;
72
+ _rapierLinkifyCandidate.maxTld = max;
73
+ }
74
+ const tldSet = _rapierLinkifyCandidate.tldSet, maxTld = _rapierLinkifyCandidate.maxTld;
75
+ for (let dot = dotFirst; dot !== -1 && dot + 2 < len; dot = text.indexOf('.', dot + 1)) {
76
+ if (dot === 0) continue;
77
+ const c1 = text.charCodeAt(dot + 1) | 32;
78
+ if (!((c1 >= 0x61 && c1 <= 0x7a) || c1 > 127)) continue;
79
+ if (c1 === 0x78 && (text.charCodeAt(dot + 2) | 32) === 0x6e && text.charCodeAt(dot + 3) === 0x2d && text.charCodeAt(dot + 4) === 0x2d) return flags | 2;
80
+ let end = dot + 2;
81
+ const limit = Math.min(len, dot + 1 + maxTld + 1);
82
+ while (end < limit) {
83
+ const c = text.charCodeAt(end);
84
+ if ((c >= 0x61 && c <= 0x7a) || (c >= 0x41 && c <= 0x5a) || (c >= 0x30 && c <= 0x39) || c === 0x2d || c > 127) end++;
85
+ else break;
86
+ }
87
+ const labelLen = end - (dot + 1);
88
+ if (labelLen >= 2 && labelLen <= maxTld && tldSet.has(text.slice(dot + 1, end).toLowerCase())) return flags | 2;
89
+ }
90
+ return flags;
91
+ }
92
+ function _rapierMayContainLink(text, linkify) {
93
+ return _rapierLinkFlags(text, linkify) !== 0;
94
+ }
95
+ function _rapierLinkifyText(state) {
96
+ const md = state.md;
97
+ if (!md.options.linkify) return;
98
+ const linkify = md.linkify;
52
99
  for (let at = 0; at < state.tokens.length; at++) {
53
100
  const block = state.tokens[at];
54
- if (block.type !== 'inline' || !candidate.test(block.content)) continue;
101
+ if (block.type !== 'inline' || !_rapierMayContainLink(block.content, linkify)) continue;
55
102
  const children = block.children;
56
103
  const source = block.content;
57
104
  let htmlDepth = 0, wholeSource, changes;
@@ -66,8 +113,8 @@ function _rapierLinkifyText(state) {
66
113
  if (/^<a[>\s]/i.test(child.content) && htmlDepth > 0) htmlDepth--;
67
114
  if (/^<\/a\s*>/i.test(child.content)) htmlDepth++;
68
115
  }
69
- if (htmlDepth || child.type !== 'text' || !candidate.test(child.content)) continue;
70
- let matches = md.linkify.match(child.content);
116
+ if (htmlDepth || child.type !== 'text' || !_rapierMayContainLink(child.content, linkify)) continue;
117
+ let matches = linkify.match(child.content);
71
118
  if (!matches?.length) continue;
72
119
  wholeSource ??= md.linkify.test(source);
73
120
  if (!wholeSource) break;
@@ -202,16 +249,24 @@ function _rapierApplyMarkdownSpec(instance, root, spec = RAPIER_MARKDOWN_SPEC) {
202
249
  {
203
250
  const taskLists = instance.core.ruler.__rules__.find(rule => rule.name === 'github-task-lists').fn;
204
251
  instance.core.ruler.at('github-task-lists', function rapierTaskLists(state) {
205
- const stranded = [], levels = new Set();
252
+ if (state.src.indexOf('[') < 0) return;
253
+ const stranded = [];
254
+ let levelMask = 0;
255
+ const levels = new Set();
206
256
  for (let at = 0; at < state.tokens.length; at++) {
207
- const token = state.tokens[at];
208
- if (token.type === 'list_item_open' && !levels.has(token.level - 1)) stranded.push(token);
209
- levels.add(token.level);
257
+ const token = state.tokens[at], lv = token.level;
258
+ if (token.type === 'list_item_open') {
259
+ const prev = lv - 1;
260
+ if (!(prev >= 0 && prev < 31 ? (levelMask & (1 << prev)) : levels.has(prev))) stranded.push(token);
261
+ }
262
+ if (lv >= 0 && lv < 31) levelMask |= (1 << lv);
263
+ else levels.add(lv);
210
264
  }
211
265
  for (const item of stranded) item.type = '';
212
266
  try { taskLists(state); } finally { for (const item of stranded) item.type = 'list_item_open'; }
213
267
  });
214
268
  instance.core.ruler.after('github-task-lists', 'rapier-empty-task-lists', function rapierEmptyTaskLists(state) {
269
+ if (state.src.indexOf('[') < 0 && state.src.indexOf('"type="checkbox"') < 0) return;
215
270
  const tokens = state.tokens || [];
216
271
  for (let index = 0; index < tokens.length; index++) {
217
272
  const token = tokens[index];
@@ -305,7 +360,8 @@ function _rapierApplyMarkdownSpec(instance, root, spec = RAPIER_MARKDOWN_SPEC) {
305
360
  const originalHtmlBlock = htmlBlockRule.fn;
306
361
  const inkAwareHtmlBlock = function rapierInkAwareHtmlBlock(state, startLine, endLine, silent) {
307
362
  const start = state.bMarks[startLine] + state.tShift[startLine];
308
- if (state.src.charCodeAt(start) === 0x3C && rapierMatchInkOpen(state.src.slice(start, start + 2048))) return false;
363
+ if (state.src.charCodeAt(start) !== 0x3C) return false;
364
+ if (state.src.startsWith(INK_OPEN_PREFIX, start) && rapierMatchInkOpen(state.src.slice(start, start + 2048))) return false;
309
365
  return originalHtmlBlock(state, startLine, endLine, silent);
310
366
  };
311
367
  inkAwareHtmlBlock.rapierInkAware = true;
@@ -313,9 +369,10 @@ function _rapierApplyMarkdownSpec(instance, root, spec = RAPIER_MARKDOWN_SPEC) {
313
369
  }
314
370
  rapierTypography.install(instance);
315
371
  instance.core.ruler.after('rapier-typography', 'rapier-ink', function rapierInkPairs(state) {
372
+ if (state.src.indexOf(INK_OPEN_PREFIX) < 0) return;
316
373
  for (let at = 0; at < state.tokens.length; at++) {
317
374
  const blockToken = state.tokens[at];
318
- if (blockToken.type !== 'inline' || !Array.isArray(blockToken.children) || blockToken.content.indexOf('<') < 0) continue;
375
+ if (blockToken.type !== 'inline' || !Array.isArray(blockToken.children) || blockToken.content.indexOf(INK_OPEN_PREFIX) < 0) continue;
319
376
  const children = blockToken.children;
320
377
  const stack = [];
321
378
  let depth = 0;
@@ -348,6 +405,7 @@ function _rapierApplyMarkdownSpec(instance, root, spec = RAPIER_MARKDOWN_SPEC) {
348
405
  }
349
406
  {
350
407
  instance.core.ruler.after('block', 'rapier-page-break', function rapierPageBreak(state) {
408
+ if (state.src.indexOf(PAGE_BREAK_MARKER) < 0) return;
351
409
  for (let at = 0; at < state.tokens.length; at++) {
352
410
  const token = state.tokens[at];
353
411
  if (token.type !== 'html_block' || !rapierIsPageBreakBlock(token.content)) continue;
@@ -411,9 +469,90 @@ function _rapierApplyMarkdownSpec(instance, root, spec = RAPIER_MARKDOWN_SPEC) {
411
469
  match.url = www ? 'http://' + raw : raw;
412
470
  return match;
413
471
  };
414
- const nativeMatch = linkify.match;
472
+ const nativeMatch = linkify.match, nativeTest = linkify.test;
473
+ let MatchCtor = null;
474
+ linkify.test = function (source) {
475
+ const flags = _rapierLinkFlags(source, this);
476
+ if (!flags) return false;
477
+ return nativeTest.call(this, source);
478
+ };
415
479
  linkify.match = function (source) {
416
- const matches = nativeMatch.call(this, source);
480
+ const flags = _rapierLinkFlags(source, this);
481
+ if (!flags) return null;
482
+ if (!MatchCtor) {
483
+ const sample = nativeMatch.call(this, 'http://a.com');
484
+ if (sample?.[0]) MatchCtor = Object.getPrototypeOf(sample[0]).constructor;
485
+ }
486
+ let matches;
487
+ if (!MatchCtor || flags === 7) {
488
+ matches = nativeMatch.call(this, source);
489
+ } else {
490
+ const list = [];
491
+ const r = (flags & 1) ? this.re.get_schema_search() : null;
492
+ const n = (flags & 2) && this.__opts__.fuzzyLink && this.__schemas__['http:'] ? this.re.get_fuzzy_link_search() : null;
493
+ const s = (flags & 4) && this.__opts__.fuzzyEmail && this.__schemas__['mailto:'] ? this.re.get_fuzzy_mail_host_search() : null;
494
+ const o = s ? this.re.get_mail_name_validator() : null;
495
+ if (r) r.lastIndex = 0;
496
+ if (n) n.lastIndex = 0;
497
+ if (s) s.lastIndex = 0;
498
+ let i, a, u, c = !r, l = !n, h = !s, p = 0;
499
+ for (;;) {
500
+ const f = Math.max(p - 1, 0);
501
+ if (s && o && !h && (!a || a.index < p)) {
502
+ if (s.lastIndex < f) s.lastIndex = f;
503
+ for (;;) {
504
+ const t = s.exec(source);
505
+ if (!t) { h = true; a = undefined; break; }
506
+ const m = o.exec(source.slice(Math.max(0, t.index - 65), t.index));
507
+ if (m) {
508
+ a = {schema: 'mailto:', index: t.index - m[1].length, lastIndex: t.index + t[0].length};
509
+ if (a.index >= p) break;
510
+ if (s.lastIndex < f) s.lastIndex = f;
511
+ }
512
+ }
513
+ }
514
+ if (n && !l && (!i || i.index < p)) {
515
+ if (n.lastIndex < f) n.lastIndex = f;
516
+ for (;;) {
517
+ const t = n.exec(source);
518
+ if (!t) { l = true; i = undefined; break; }
519
+ i = {schema: '', index: t.index + t[1].length, lastIndex: t.index + t[0].length};
520
+ if (i.index >= p) break;
521
+ if (n.lastIndex < f) n.lastIndex = f;
522
+ }
523
+ }
524
+ let d, g = a;
525
+ if (!g || (i && (i.index < g.index || (i.index === g.index && i.lastIndex > g.lastIndex)))) g = i;
526
+ if (!c) {
527
+ for (;;) {
528
+ if (!u) {
529
+ if (r.lastIndex < f) r.lastIndex = f;
530
+ const t = r.exec(source);
531
+ if (!t) { c = true; break; }
532
+ u = {schema: t[2], index: t.index + t[1].length, lastIndex: t.index + t[0].length};
533
+ }
534
+ if (u.index < p) { u = undefined; continue; }
535
+ if (g && u.index > g.index) break;
536
+ const t = u;
537
+ u = undefined;
538
+ const len = this.testSchemaAt(source, t.schema, t.lastIndex);
539
+ if (len) { d = {schema: t.schema, index: t.index, lastIndex: t.lastIndex + len}; break; }
540
+ }
541
+ }
542
+ let m = d;
543
+ if (!m || (a && (a.index < m.index || (a.index === m.index && a.lastIndex > m.lastIndex)))) m = a;
544
+ if (!m || (i && (i.index < m.index || (i.index === m.index && i.lastIndex > m.lastIndex)))) m = i;
545
+ if (!m) break;
546
+ if (m === a) a = undefined;
547
+ else if (m === i) i = undefined;
548
+ const item = new MatchCtor(source, m.schema, m.index, m.lastIndex);
549
+ if (item.schema) this.__schemas__[item.schema].normalize(item, this);
550
+ else this.normalize(item);
551
+ list.push(item);
552
+ p = m.lastIndex;
553
+ }
554
+ matches = list.length ? list : null;
555
+ }
417
556
  if (!matches) return matches;
418
557
  const result = [];
419
558
  let end = 0;
@@ -428,18 +567,20 @@ function _rapierApplyMarkdownSpec(instance, root, spec = RAPIER_MARKDOWN_SPEC) {
428
567
  linkify.matchAtStart = function (source) { return refineLink(nativeMatchAtStart.call(this, source), source); };
429
568
  const inlineLinkify = instance.inline.ruler.__rules__.find(rule => rule.name === 'linkify').fn;
430
569
  instance.inline.ruler.at('linkify', function rapierInlineLinkify(state, silent) {
431
- if (state.src.charCodeAt(state.pos) === 0x3a) {
432
- const prefix = state.src.slice(Math.max(0, state.pos - 5), state.pos).toLowerCase();
433
- const width = prefix.endsWith('https') ? 5 : prefix.endsWith('http') ? 4 : 0;
434
- if (width && !allowedStart(state.src, state.pos - width)) return false;
435
- }
570
+ const pos = state.pos, src = state.src;
571
+ if (pos + 3 > state.posMax || src.charCodeAt(pos) !== 0x3a || src.charCodeAt(pos + 1) !== 0x2f || src.charCodeAt(pos + 2) !== 0x2f) return false;
572
+ const prefix = src.slice(Math.max(0, pos - 5), pos).toLowerCase();
573
+ const width = prefix.endsWith('https') ? 5 : prefix.endsWith('http') ? 4 : 0;
574
+ if (width && !allowedStart(src, pos - width)) return false;
436
575
  return inlineLinkify(state, silent);
437
576
  });
438
577
  }
439
578
  const disallowedTag = /<\/?(?:title|textarea|style|xmp|iframe|noembed|noframes|script|plaintext)(?=[ \t\n\v\f\r/>])/gi;
579
+ const disallowedTest = /<\/?(?:title|textarea|style|xmp|iframe|noembed|noframes|script|plaintext)(?=[ \t\n\v\f\r/>])/i;
440
580
  instance.core.ruler.push('rapier-tagfilter', function rapierTagfilter(state) {
581
+ if (state.src.indexOf('<') < 0 || !disallowedTest.test(state.src)) return;
441
582
  const filter = token => {
442
- if (token.type === 'html_inline' || token.type === 'html_block') {
583
+ if ((token.type === 'html_inline' || token.type === 'html_block') && disallowedTest.test(token.content)) {
443
584
  token.content = token.content.replace(disallowedTag, tag => '&lt;' + tag.slice(1));
444
585
  }
445
586
  };
@@ -449,6 +590,15 @@ function _rapierApplyMarkdownSpec(instance, root, spec = RAPIER_MARKDOWN_SPEC) {
449
590
  if (token.type === 'inline' && token.content.indexOf('<') >= 0) for (const child of token.children || []) filter(child);
450
591
  }
451
592
  });
593
+ const emojiRule = instance.core.ruler.__rules__.find(rule => rule.name === 'emoji');
594
+ if (emojiRule) {
595
+ const origEmoji = emojiRule.fn;
596
+ const emojiQuickRe = /:|<3|<\/3|<\\3|[8BXx][-']?[()]/;
597
+ instance.core.ruler.at('emoji', function rapierEmoji(state) {
598
+ if (!emojiQuickRe.test(state.src)) return;
599
+ origEmoji(state);
600
+ });
601
+ }
452
602
  if (spec.core && spec.core.retainReferenceDefinitions) {
453
603
  instance.core.ruler.disable('strip_references');
454
604
  }