securemark 0.268.0 → 0.268.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.268.1
4
+
5
+ - Refactoring.
6
+
3
7
  ## 0.268.0
4
8
 
5
9
  - Add lineurl syntax.
package/README.md CHANGED
@@ -31,10 +31,6 @@ Secure markdown renderer working on browsers for user input data.
31
31
  - Audio (.oga, .ogg)
32
32
  - Images
33
33
 
34
- ## Demos
35
-
36
- https://falsandtru.github.io/securemark/
37
-
38
34
  ## APIs
39
35
 
40
36
  [index.d.ts](index.d.ts)
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! securemark v0.268.0 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
1
+ /*! securemark v0.268.1 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
2
2
  (function webpackUniversalModuleDefinition(root, factory) {
3
3
  if(typeof exports === 'object' && typeof module === 'object')
4
4
  module.exports = factory(require("Prism"), require("DOMPurify"));
@@ -5234,7 +5234,7 @@ const openers = {
5234
5234
  '(': /^\(([0-9]*|[a-z]*)(?![^)\n])\)?(?:-(?!-)[0-9]*)*(?:$|\s)/
5235
5235
  };
5236
5236
  exports.olist = (0, combinator_1.lazy)(() => (0, combinator_1.block)((0, combinator_1.validate)(new RegExp([/^([0-9]+|[a-z]+|[A-Z]+)(?:-[0-9]+)*\.(?=[^\S\n]|\n[^\S\n]*\S)/.source, /^\(([0-9]+|[a-z]+)\)(?:-[0-9]+)*(?=[^\S\n]|\n[^\S\n]*\S)/.source].join('|')), (0, combinator_1.state)(8 /* State.media */, exports.olist_))));
5237
- exports.olist_ = (0, combinator_1.lazy)(() => (0, combinator_1.block)((0, combinator_1.union)([(0, combinator_1.match)(openers['.'], (0, memoize_1.memoize)(ms => list(type(ms[1]), '.'), ms => type(ms[1]).charCodeAt(0) || 0, [])), (0, combinator_1.match)(openers['('], (0, memoize_1.memoize)(ms => list(type(ms[1]), '('), ms => type(ms[1]).charCodeAt(0) || 0, []))])));
5237
+ exports.olist_ = (0, combinator_1.lazy)(() => (0, combinator_1.block)((0, combinator_1.union)([(0, combinator_1.match)(openers['.'], (0, memoize_1.memoize)(ms => list(type(ms[1]), '.'), ms => index(ms[1]), [])), (0, combinator_1.match)(openers['('], (0, memoize_1.memoize)(ms => list(type(ms[1]), '('), ms => index(ms[1]), []))])));
5238
5238
  const list = (type, form) => (0, combinator_1.fmap)((0, combinator_1.some)((0, combinator_1.creation)(1, false, (0, combinator_1.union)([(0, inline_1.indexee)((0, combinator_1.fmap)((0, combinator_1.fallback)((0, combinator_1.inits)([(0, combinator_1.line)((0, combinator_1.open)(heads[form], (0, combinator_1.subsequence)([ulist_1.checkbox, (0, visibility_1.trimBlank)((0, visibility_1.visualize)((0, combinator_1.some)((0, combinator_1.union)([inline_1.indexer, inline_1.inline]))))]), true)), (0, combinator_1.indent)((0, combinator_1.union)([ulist_1.ulist_, exports.olist_, ilist_1.ilist_]))]), exports.invalid), ns => [(0, dom_1.html)('li', {
5239
5239
  'data-marker': ns[0] || undefined
5240
5240
  }, (0, dom_1.defrag)((0, ulist_1.fillFirstLine)((0, array_1.shift)(ns)[1])))]), true)]))), es => [format((0, dom_1.html)('ol', es), type, form)]);
@@ -5252,12 +5252,26 @@ exports.invalid = (0, combinator_1.rewrite)((0, combinator_1.inits)([source_1.co
5252
5252
  source
5253
5253
  }) => [['', (0, dom_1.html)('span', {
5254
5254
  class: 'invalid',
5255
- 'data-invalid-syntax': 'listitem',
5255
+ 'data-invalid-syntax': 'list',
5256
5256
  'data-invalid-type': 'syntax',
5257
5257
  'data-invalid-message': 'Fix the indent or the head of the list item'
5258
5258
  }, source.replace('\n', ''))], '']);
5259
- function type(index) {
5260
- switch (index) {
5259
+ function index(value) {
5260
+ switch (value) {
5261
+ case 'i':
5262
+ return 1;
5263
+ case 'a':
5264
+ return 2;
5265
+ case 'I':
5266
+ return 3;
5267
+ case 'A':
5268
+ return 4;
5269
+ default:
5270
+ return 0;
5271
+ }
5272
+ }
5273
+ function type(value) {
5274
+ switch (value) {
5261
5275
  case 'i':
5262
5276
  return 'i';
5263
5277
  case 'a':
@@ -5284,7 +5298,7 @@ function style(type) {
5284
5298
  return '';
5285
5299
  }
5286
5300
  }
5287
- function initial(type) {
5301
+ function pattern(type) {
5288
5302
  switch (type) {
5289
5303
  case 'i':
5290
5304
  return /^\(?i[).]?$/;
@@ -5298,27 +5312,38 @@ function initial(type) {
5298
5312
  return /^\(?[01][).]?$/;
5299
5313
  }
5300
5314
  }
5301
- function format(el, type, form) {
5302
- if (el.firstElementChild?.firstElementChild?.className === 'checkbox') {
5303
- el.setAttribute('class', 'checklist');
5315
+ function format(list, type, form) {
5316
+ if (list.firstElementChild?.firstElementChild?.classList.contains('checkbox')) {
5317
+ list.classList.add('checklist');
5304
5318
  }
5305
- (0, dom_1.define)(el, {
5319
+ (0, dom_1.define)(list, {
5306
5320
  type: type || undefined,
5307
5321
  'data-format': form === '.' ? undefined : 'paren',
5308
5322
  'data-type': style(type) || undefined
5309
5323
  });
5310
- const marker = el.firstElementChild?.getAttribute('data-marker').match(initial(type))?.[0] ?? '';
5311
- for (let es = el.children, len = es.length, i = 0; i < len; ++i) {
5312
- const el = es[i];
5313
- switch (el.getAttribute('data-marker')) {
5314
- case '':
5315
- case marker:
5316
- el.removeAttribute('data-marker');
5324
+ const marker = list.firstElementChild?.getAttribute('data-marker') ?? '';
5325
+ // TODO: CSSカウンターをattr(start)でリセットできるようになればstart値からのオートインクリメントに対応させる。
5326
+ const start = marker.match(pattern(type))?.[0] ?? '';
5327
+ for (let es = list.children, len = es.length, i = 0; i < len; ++i) {
5328
+ const item = es[i];
5329
+ switch (item.getAttribute('data-marker')) {
5330
+ case null:
5317
5331
  continue;
5332
+ case start:
5333
+ item.removeAttribute('data-marker');
5334
+ continue;
5335
+ case marker:
5336
+ if (i === 0 || item.classList.contains('invalid')) continue;
5337
+ (0, dom_1.define)(item, {
5338
+ class: 'invalid',
5339
+ 'data-invalid-syntax': 'list',
5340
+ 'data-invalid-type': 'index',
5341
+ 'data-invalid-message': 'Fix the duplicate index'
5342
+ });
5318
5343
  }
5319
5344
  break;
5320
5345
  }
5321
- return el;
5346
+ return list;
5322
5347
  }
5323
5348
 
5324
5349
  /***/ }),
@@ -5587,11 +5612,11 @@ function fillFirstLine(ns) {
5587
5612
  return ns.length === 1 && typeof ns[0] === 'object' && ['UL', 'OL'].includes(ns[0].tagName) ? (0, array_1.unshift)([(0, dom_1.html)('br')], ns) : ns;
5588
5613
  }
5589
5614
  exports.fillFirstLine = fillFirstLine;
5590
- function format(el) {
5591
- if (el.firstElementChild?.firstElementChild?.className === 'checkbox') {
5592
- el.setAttribute('class', 'checklist');
5615
+ function format(list) {
5616
+ if (list.firstElementChild?.firstElementChild?.classList.contains('checkbox')) {
5617
+ list.classList.add('checklist');
5593
5618
  }
5594
- return el;
5619
+ return list;
5595
5620
  }
5596
5621
 
5597
5622
  /***/ }),
@@ -6128,6 +6153,7 @@ function identity(id, text, name = 'index') {
6128
6153
  }
6129
6154
  exports.identity = identity;
6130
6155
  function text(source, optional = false) {
6156
+ if (!source.firstChild) return '';
6131
6157
  const indexer = source.querySelector(':scope > .indexer');
6132
6158
  const index = indexer?.getAttribute('data-index');
6133
6159
  if (index) return index;
@@ -6959,8 +6985,7 @@ function* figure(target, footnotes, opts = {}) {
6959
6985
  }
6960
6986
  labels.add(label);
6961
6987
  opts.id !== '' && def.setAttribute('id', `label:${opts.id ? `${opts.id}:` : ''}${label}`);
6962
- for (let rs = refs.take(label, Infinity), i = 0; i < rs.length; ++i) {
6963
- const ref = rs[i];
6988
+ for (const ref of refs.take(label, Infinity)) {
6964
6989
  if (ref.getAttribute('data-invalid-message') === messages.reference) {
6965
6990
  (0, dom_1.define)(ref, {
6966
6991
  class: void ref.classList.remove('invalid'),
@@ -7026,7 +7051,6 @@ Object.defineProperty(exports, "__esModule", ({
7026
7051
  }));
7027
7052
  exports.reference = exports.annotation = exports.footnote = void 0;
7028
7053
  const indexee_1 = __webpack_require__(1269);
7029
- const queue_1 = __webpack_require__(4934);
7030
7054
  const dom_1 = __webpack_require__(3252);
7031
7055
  function* footnote(target, footnotes, opts = {}, bottom = null) {
7032
7056
  for (let es = target.querySelectorAll(`.annotations`), len = es.length, i = 0; i < len; ++i) {
@@ -7040,23 +7064,35 @@ function* footnote(target, footnotes, opts = {}, bottom = null) {
7040
7064
  exports.footnote = footnote;
7041
7065
  exports.annotation = build('annotation', n => `*${n}`, 'h1, h2, h3, h4, h5, h6, aside.aside, hr');
7042
7066
  exports.reference = build('reference', (n, abbr) => `[${abbr || n}]`);
7043
- function build(syntax, marker, splitter) {
7067
+ function build(syntax, marker, splitter = '_') {
7044
7068
  // Referenceを含むAnnotationの重複排除は両構文が互いに処理済みであることを必要とするため
7045
7069
  // 構文ごとに各1回の処理では不可能
7046
7070
  return function* (target, footnote, opts = {}, bottom = null) {
7047
7071
  const defs = new Map();
7048
- const buffer = new queue_1.MultiQueue();
7049
- const titles = new Map();
7050
7072
  const splitters = [];
7051
- for (let es = target.querySelectorAll(splitter ?? '_'), len = es.length, i = 0; i < len; ++i) {
7073
+ for (let es = target.querySelectorAll(splitter), len = es.length, i = 0; i < len; ++i) {
7074
+ if (i % 100 === 0) yield;
7052
7075
  const el = es[i];
7053
7076
  el.parentNode === target && splitters.push(el);
7054
7077
  }
7078
+ const refs = target.querySelectorAll(`sup.${syntax}:not(.disabled)`);
7079
+ const titles = new Map();
7080
+ const contents = new Map();
7081
+ for (let len = refs.length, i = 0; i < len; ++i) {
7082
+ if (i % 10 === 9) yield;
7083
+ const ref = refs[i];
7084
+ const identifier = ref.getAttribute('data-abbr') || ` ${ref.firstElementChild.innerHTML}`;
7085
+ if (titles.has(identifier)) continue;
7086
+ const content = (0, dom_1.frag)(ref.firstElementChild.cloneNode(true).childNodes);
7087
+ const title = (0, indexee_1.text)(content).trim();
7088
+ if (!title) continue;
7089
+ titles.set(identifier, title);
7090
+ contents.set(identifier, content);
7091
+ }
7055
7092
  let count = 0;
7056
7093
  let total = 0;
7057
7094
  let style;
7058
- for (let refs = target.querySelectorAll(`sup.${syntax}:not(.disabled)`), len = refs.length, i = 0; i < len; ++i) {
7059
- yield;
7095
+ for (let len = refs.length, i = 0; i < len; ++i) {
7060
7096
  const ref = refs[i];
7061
7097
  while (splitters.length > 0 && splitters[0].compareDocumentPosition(ref) & Node.DOCUMENT_POSITION_FOLLOWING) {
7062
7098
  if (defs.size > 0) {
@@ -7071,7 +7107,6 @@ function build(syntax, marker, splitter) {
7071
7107
  }
7072
7108
  const identifier = ref.getAttribute('data-abbr') || ` ${ref.firstElementChild.innerHTML}`;
7073
7109
  const abbr = ref.getAttribute('data-abbr') || undefined;
7074
- const content = (0, dom_1.frag)(ref.firstElementChild.cloneNode(true).childNodes);
7075
7110
  style ??= abbr ? 'abbr' : 'count';
7076
7111
  if (style === 'count' ? abbr : !abbr) {
7077
7112
  (0, dom_1.define)(ref, {
@@ -7093,29 +7128,14 @@ function build(syntax, marker, splitter) {
7093
7128
  } else {
7094
7129
  ref.lastChild?.remove();
7095
7130
  }
7096
- const title = titles.get(identifier) || (0, indexee_1.text)(content).trim() || undefined;
7097
- title ? !titles.has(identifier) && titles.set(identifier, title) : buffer.set(identifier, ref);
7098
- const blank = !!abbr && !content.firstChild;
7131
+ const title = titles.get(identifier);
7132
+ const content = (0, dom_1.frag)(ref.firstElementChild.cloneNode(true).childNodes);
7099
7133
  const refIndex = ++count;
7100
7134
  const refId = opts.id !== '' ? `${syntax}:${opts.id ?? ''}:ref:${refIndex}` : undefined;
7101
7135
  const def = false || defs.get(identifier) || defs.set(identifier, (0, dom_1.html)('li', {
7102
7136
  id: opts.id !== '' ? `${syntax}:${opts.id ?? ''}:def:${total + defs.size + 1}` : undefined,
7103
7137
  'data-marker': !footnote ? marker(total + defs.size + 1, abbr) : undefined
7104
- }, [content.cloneNode(true), (0, dom_1.html)('sup')])).get(identifier);
7105
- if (title && !blank && def.childNodes.length === 1) {
7106
- def.insertBefore(content.cloneNode(true), def.lastChild);
7107
- for (let refs = buffer.take(identifier, Infinity), i = 0; i < refs.length; ++i) {
7108
- const ref = refs[i];
7109
- if (ref.getAttribute('data-invalid-type') !== 'content') continue;
7110
- (0, dom_1.define)(ref, {
7111
- title,
7112
- class: void ref.classList.remove('invalid'),
7113
- 'data-invalid-syntax': null,
7114
- 'data-invalid-type': null,
7115
- 'data-invalid-message': null
7116
- });
7117
- }
7118
- }
7138
+ }, [contents.get(identifier) ?? (0, dom_1.frag)(), (0, dom_1.html)('sup')])).get(identifier);
7119
7139
  const defIndex = +def.id.slice(def.id.lastIndexOf(':') + 1) || total + defs.size;
7120
7140
  const defId = def.id || undefined;
7121
7141
  (0, dom_1.define)(ref, {
@@ -7135,7 +7155,7 @@ function build(syntax, marker, splitter) {
7135
7155
  }, marker(defIndex, abbr)));
7136
7156
  def.lastChild.appendChild((0, dom_1.html)('a', {
7137
7157
  href: refId && `#${refId}`,
7138
- title: abbr && !blank ? title : undefined
7158
+ title: abbr && (0, indexee_1.text)(content).trim() || undefined
7139
7159
  }, `^${refIndex}`));
7140
7160
  }
7141
7161
  if (defs.size > 0 || footnote) {
@@ -8368,7 +8388,7 @@ function unlink(h) {
8368
8388
  /***/ 3252:
8369
8389
  /***/ (function(module) {
8370
8390
 
8371
- /*! typed-dom v0.0.315 https://github.com/falsandtru/typed-dom | (c) 2016, falsandtru | (Apache-2.0 AND MPL-2.0) License */
8391
+ /*! typed-dom v0.0.316 https://github.com/falsandtru/typed-dom | (c) 2016, falsandtru | (Apache-2.0 AND MPL-2.0) License */
8372
8392
  (function webpackUniversalModuleDefinition(root, factory) {
8373
8393
  if(true)
8374
8394
  module.exports = factory();
@@ -8699,7 +8719,7 @@ exports.defrag = defrag;
8699
8719
  /***/ 6120:
8700
8720
  /***/ (function(module) {
8701
8721
 
8702
- /*! typed-dom v0.0.315 https://github.com/falsandtru/typed-dom | (c) 2016, falsandtru | (Apache-2.0 AND MPL-2.0) License */
8722
+ /*! typed-dom v0.0.316 https://github.com/falsandtru/typed-dom | (c) 2016, falsandtru | (Apache-2.0 AND MPL-2.0) License */
8703
8723
  (function webpackUniversalModuleDefinition(root, factory) {
8704
8724
  if(true)
8705
8725
  module.exports = factory();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.268.0",
3
+ "version": "0.268.1",
4
4
  "description": "Secure markdown renderer working on browsers for user input data.",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/falsandtru/securemark",
@@ -34,11 +34,11 @@
34
34
  "@types/mocha": "10.0.1",
35
35
  "@types/power-assert": "1.5.8",
36
36
  "@types/prismjs": "1.26.0",
37
- "@typescript-eslint/parser": "^5.52.0",
37
+ "@typescript-eslint/parser": "^5.53.0",
38
38
  "babel-loader": "^9.1.2",
39
39
  "babel-plugin-unassert": "^3.2.0",
40
40
  "concurrently": "^7.6.0",
41
- "eslint": "^8.34.0",
41
+ "eslint": "^8.35.0",
42
42
  "eslint-plugin-redos": "^4.4.5",
43
43
  "eslint-webpack-plugin": "^4.0.0",
44
44
  "glob": "^8.1.0",
@@ -49,11 +49,11 @@
49
49
  "karma-mocha": "^2.0.1",
50
50
  "karma-power-assert": "^1.0.0",
51
51
  "mocha": "^10.2.0",
52
- "npm-check-updates": "^16.7.4",
52
+ "npm-check-updates": "^16.7.9",
53
53
  "semver": "^7.3.8",
54
54
  "spica": "0.0.719",
55
55
  "ts-loader": "^9.4.2",
56
- "typed-dom": "^0.0.315",
56
+ "typed-dom": "^0.0.316",
57
57
  "typescript": "4.9.5",
58
58
  "webpack": "^5.75.0",
59
59
  "webpack-cli": "^5.0.1",
@@ -1,10 +1,9 @@
1
1
  import { autolink } from './autolink';
2
- import { some } from '../combinator';
3
2
  import { inspect } from '../debug.test';
4
3
 
5
4
  describe('Unit: parser/autolink', () => {
6
5
  describe('autolink', () => {
7
- const parser = (source: string) => some(autolink)({ source, context: {} });
6
+ const parser = (source: string) => autolink({ source, context: {} });
8
7
 
9
8
  it('basic', () => {
10
9
  assert.deepStrictEqual(inspect(parser(' http://host')), [[' ', '<a class="url" href="http://host" target="_blank">http://host</a>'], '']);
@@ -30,7 +30,7 @@ describe('Unit: parser/block/olist', () => {
30
30
  assert.deepStrictEqual(inspect(parser('(1)\n')), undefined);
31
31
  assert.deepStrictEqual(inspect(parser('(I) ')), undefined);
32
32
  assert.deepStrictEqual(inspect(parser('(A) ')), undefined);
33
- assert.deepStrictEqual(inspect(parser(' 1.')), undefined);
33
+ assert.deepStrictEqual(inspect(parser(' 1. ')), undefined);
34
34
  });
35
35
 
36
36
  it('single', () => {
@@ -91,7 +91,7 @@ describe('Unit: parser/block/olist', () => {
91
91
  assert.deepStrictEqual(inspect(parser('01. ')), [['<ol><li data-marker="01."></li></ol>'], '']);
92
92
  assert.deepStrictEqual(inspect(parser('0.\n1')), [['<ol><li></li><li data-marker="1."></li></ol>'], '']);
93
93
  assert.deepStrictEqual(inspect(parser('8.\n9')), [['<ol><li data-marker="8."></li><li data-marker="9."></li></ol>'], '']);
94
- assert.deepStrictEqual(inspect(parser('9.\n9')), [['<ol><li data-marker="9."></li><li data-marker="9."></li></ol>'], '']);
94
+ assert.deepStrictEqual(inspect(parser('9.\n9')), [['<ol><li data-marker="9."></li><li data-marker="9." class="invalid"></li></ol>'], '']);
95
95
  });
96
96
 
97
97
  it('branch', () => {
@@ -27,10 +27,10 @@ export const olist: OListParser = lazy(() => block(validate(
27
27
  export const olist_: OListParser = lazy(() => block(union([
28
28
  match(
29
29
  openers['.'],
30
- memoize(ms => list(type(ms[1]), '.'), ms => type(ms[1]).charCodeAt(0) || 0, [])),
30
+ memoize(ms => list(type(ms[1]), '.'), ms => index(ms[1]), [])),
31
31
  match(
32
32
  openers['('],
33
- memoize(ms => list(type(ms[1]), '('), ms => type(ms[1]).charCodeAt(0) || 0, [])),
33
+ memoize(ms => list(type(ms[1]), '('), ms => index(ms[1]), [])),
34
34
  ])));
35
35
 
36
36
  const list = (type: string, form: string): OListParser.ListParser => fmap(
@@ -61,14 +61,29 @@ export const invalid = rewrite(
61
61
  '',
62
62
  html('span', {
63
63
  class: 'invalid',
64
- 'data-invalid-syntax': 'listitem',
64
+ 'data-invalid-syntax': 'list',
65
65
  'data-invalid-type': 'syntax',
66
66
  'data-invalid-message': 'Fix the indent or the head of the list item',
67
67
  }, source.replace('\n', ''))
68
68
  ], '']);
69
69
 
70
- function type(index: string): string {
71
- switch (index) {
70
+ function index(value: string): number {
71
+ switch (value) {
72
+ case 'i':
73
+ return 1;
74
+ case 'a':
75
+ return 2;
76
+ case 'I':
77
+ return 3;
78
+ case 'A':
79
+ return 4;
80
+ default:
81
+ return 0;
82
+ }
83
+ }
84
+
85
+ function type(value: string): string {
86
+ switch (value) {
72
87
  case 'i':
73
88
  return 'i';
74
89
  case 'a':
@@ -97,7 +112,7 @@ function style(type: string): string {
97
112
  }
98
113
  }
99
114
 
100
- function initial(type: string): RegExp {
115
+ function pattern(type: string): RegExp {
101
116
  switch (type) {
102
117
  case 'i':
103
118
  return /^\(?i[).]?$/;
@@ -112,25 +127,37 @@ function initial(type: string): RegExp {
112
127
  }
113
128
  }
114
129
 
115
- function format(el: HTMLOListElement, type: string, form: string): HTMLOListElement {
116
- if (el.firstElementChild?.firstElementChild?.className === 'checkbox') {
117
- el.setAttribute('class', 'checklist');
130
+ function format(list: HTMLOListElement, type: string, form: string): HTMLOListElement {
131
+ if (list.firstElementChild?.firstElementChild?.classList.contains('checkbox')) {
132
+ list.classList.add('checklist');
118
133
  }
119
- define(el, {
134
+ define(list, {
120
135
  type: type || undefined,
121
136
  'data-format': form === '.' ? undefined : 'paren',
122
137
  'data-type': style(type) || undefined,
123
138
  });
124
- const marker = el.firstElementChild?.getAttribute('data-marker')!.match(initial(type))?.[0] ?? '';
125
- for (let es = el.children, len = es.length, i = 0; i < len; ++i) {
126
- const el = es[i];
127
- switch (el.getAttribute('data-marker')) {
128
- case '':
129
- case marker:
130
- el.removeAttribute('data-marker');
139
+ const marker = list.firstElementChild?.getAttribute('data-marker') ?? '';
140
+ // TODO: CSSカウンターをattr(start)でリセットできるようになればstart値からのオートインクリメントに対応させる。
141
+ const start = marker.match(pattern(type))?.[0] ?? '';
142
+ for (let es = list.children, len = es.length, i = 0; i < len; ++i) {
143
+ const item = es[i];
144
+ assert(item.getAttribute('data-marker') !== '');
145
+ switch (item.getAttribute('data-marker')) {
146
+ case null:
131
147
  continue;
148
+ case start:
149
+ item.removeAttribute('data-marker');
150
+ continue;
151
+ case marker:
152
+ if (i === 0 || item.classList.contains('invalid')) continue;
153
+ define(item, {
154
+ class: 'invalid',
155
+ 'data-invalid-syntax': 'list',
156
+ 'data-invalid-type': 'index',
157
+ 'data-invalid-message': 'Fix the duplicate index',
158
+ });
132
159
  }
133
160
  break;
134
161
  }
135
- return el;
162
+ return list;
136
163
  }
@@ -17,7 +17,7 @@ describe('Unit: parser/block/ulist', () => {
17
17
  assert.deepStrictEqual(inspect(parser('-[ ]')), undefined);
18
18
  assert.deepStrictEqual(inspect(parser('-[x]')), undefined);
19
19
  assert.deepStrictEqual(inspect(parser('-\n')), undefined);
20
- assert.deepStrictEqual(inspect(parser(' -')), undefined);
20
+ assert.deepStrictEqual(inspect(parser(' - ')), undefined);
21
21
  assert.deepStrictEqual(inspect(parser('+')), undefined);
22
22
  assert.deepStrictEqual(inspect(parser('*')), undefined);
23
23
  });
@@ -40,9 +40,9 @@ export function fillFirstLine(ns: (HTMLElement | string)[]): (HTMLElement | stri
40
40
  : ns;
41
41
  }
42
42
 
43
- function format(el: HTMLUListElement): HTMLUListElement {
44
- if (el.firstElementChild?.firstElementChild?.className === 'checkbox') {
45
- el.setAttribute('class', 'checklist');
43
+ function format(list: HTMLUListElement): HTMLUListElement {
44
+ if (list.firstElementChild?.firstElementChild?.classList.contains('checkbox')) {
45
+ list.classList.add('checklist');
46
46
  }
47
- return el;
47
+ return list;
48
48
  }
@@ -33,6 +33,7 @@ assert(identity(undefined, '0'.repeat(200) + 1, 'mark')!.slice(6) === '0'.repeat
33
33
  export function text(source: HTMLElement | DocumentFragment, optional = false): string {
34
34
  assert(source instanceof DocumentFragment || !source.matches('.indexer'));
35
35
  assert(source.querySelectorAll(':scope > .indexer').length <= 1);
36
+ if (!source.firstChild) return '';
36
37
  const indexer = source.querySelector(':scope > .indexer');
37
38
  const index = indexer?.getAttribute('data-index');
38
39
  if (index) return index;
@@ -139,8 +139,7 @@ export function* figure(
139
139
  }
140
140
  labels.add(label);
141
141
  opts.id !== '' && def.setAttribute('id', `label:${opts.id ? `${opts.id}:` : ''}${label}`);
142
- for (let rs = refs.take(label, Infinity), i = 0; i < rs.length; ++i) {
143
- const ref = rs[i];
142
+ for (const ref of refs.take(label, Infinity)) {
144
143
  if (ref.getAttribute('data-invalid-message') === messages.reference) {
145
144
  define(ref, {
146
145
  class: void ref.classList.remove('invalid'),
@@ -22,7 +22,7 @@ describe('Unit: parser/processor/footnote', () => {
22
22
  const target = parse('((a b))');
23
23
  const footnote = html('ol');
24
24
  for (let i = 0; i < 3; ++i) {
25
- assert.deepStrictEqual([...annotation(target, footnote)].length, i === 0 ? 3 : 2);
25
+ assert.deepStrictEqual([...annotation(target, footnote)].length, i === 0 ? 2 : 1);
26
26
  assert.deepStrictEqual(
27
27
  [...target.children].map(el => el.outerHTML),
28
28
  [
@@ -48,7 +48,7 @@ describe('Unit: parser/processor/footnote', () => {
48
48
  const target = parse('((1))((12345678901234567890))');
49
49
  const footnote = html('ol');
50
50
  for (let i = 0; i < 3; ++i) {
51
- assert.deepStrictEqual([...annotation(target, footnote)].length, i === 0 ? 6 : 4);
51
+ assert.deepStrictEqual([...annotation(target, footnote)].length, i === 0 ? 4 : 2);
52
52
  assert.deepStrictEqual(
53
53
  [...target.children].map(el => el.outerHTML),
54
54
  [
@@ -165,7 +165,7 @@ describe('Unit: parser/processor/footnote', () => {
165
165
  const target = parse('((a b))');
166
166
  const footnote = html('ol');
167
167
  for (let i = 0; i < 3; ++i) {
168
- assert.deepStrictEqual([...annotation(target, footnote, { id: '0' })].length, i === 0 ? 3 : 2);
168
+ assert.deepStrictEqual([...annotation(target, footnote, { id: '0' })].length, i === 0 ? 2 : 1);
169
169
  assert.deepStrictEqual(
170
170
  [...target.children].map(el => el.outerHTML),
171
171
  [
@@ -1,5 +1,4 @@
1
1
  import { text } from '../inline/extension/indexee';
2
- import { MultiQueue } from 'spica/queue';
3
2
  import { frag, html, define } from 'typed-dom/dom';
4
3
 
5
4
  export function* footnote(
@@ -24,7 +23,7 @@ export const reference = build('reference', (n, abbr) => `[${abbr || n}]`);
24
23
  function build(
25
24
  syntax: 'annotation' | 'reference',
26
25
  marker: (index: number, abbr: string | undefined) => string,
27
- splitter?: string,
26
+ splitter: string = '_',
28
27
  ) {
29
28
  assert(syntax.match(/^[a-z]+$/));
30
29
  // Referenceを含むAnnotationの重複排除は両構文が互いに処理済みであることを必要とするため
@@ -36,21 +35,31 @@ function build(
36
35
  bottom: Node | null = null,
37
36
  ): Generator<HTMLAnchorElement | HTMLLIElement | undefined, undefined, undefined> {
38
37
  const defs = new Map<string, HTMLLIElement>();
39
- const buffer = new MultiQueue<string, HTMLElement>();
40
- const titles = new Map<string, string>();
41
38
  const splitters: Element[] = [];
42
- for (let es = target.querySelectorAll(splitter ?? '_'),
39
+ for (let es = target.querySelectorAll(splitter),
43
40
  len = es.length, i = 0; i < len; ++i) {
41
+ if (i % 100 === 0) yield;
44
42
  const el = es[i];
45
43
  el.parentNode === target && splitters.push(el);
46
44
  }
45
+ const refs = target.querySelectorAll(`sup.${syntax}:not(.disabled)`);
46
+ const titles = new Map<string, string>();
47
+ const contents = new Map<string, DocumentFragment>();
48
+ for (let len = refs.length, i = 0; i < len; ++i) {
49
+ if (i % 10 === 9) yield;
50
+ const ref = refs[i];
51
+ const identifier = ref.getAttribute('data-abbr') || ` ${ref.firstElementChild!.innerHTML}`;
52
+ if (titles.has(identifier)) continue;
53
+ const content = frag(ref.firstElementChild!.cloneNode(true).childNodes);
54
+ const title = text(content).trim();
55
+ if (!title) continue;
56
+ titles.set(identifier, title);
57
+ contents.set(identifier, content);
58
+ }
47
59
  let count = 0;
48
60
  let total = 0;
49
61
  let style: 'count' | 'abbr';
50
- for (
51
- let refs = target.querySelectorAll(`sup.${syntax}:not(.disabled)`),
52
- len = refs.length, i = 0; i < len; ++i) {
53
- yield;
62
+ for (let len = refs.length, i = 0; i < len; ++i) {
54
63
  const ref = refs[i];
55
64
  while (splitters.length > 0
56
65
  && splitters[0].compareDocumentPosition(ref) & Node.DOCUMENT_POSITION_FOLLOWING) {
@@ -65,7 +74,6 @@ function build(
65
74
  }
66
75
  const identifier = ref.getAttribute('data-abbr') || ` ${ref.firstElementChild!.innerHTML}`;
67
76
  const abbr = ref.getAttribute('data-abbr') || undefined;
68
- const content = frag(ref.firstElementChild!.cloneNode(true).childNodes);
69
77
  style ??= abbr ? 'abbr' : 'count';
70
78
  if (style === 'count' ? abbr : !abbr) {
71
79
  define(ref, {
@@ -89,14 +97,10 @@ function build(
89
97
  else {
90
98
  ref.lastChild?.remove();
91
99
  }
92
- const title = titles.get(identifier) || text(content).trim() || undefined;
100
+ const title = titles.get(identifier);
93
101
  assert(title !== '');
94
102
  assert(syntax !== 'annotation' || title);
95
- title
96
- ? !titles.has(identifier) && titles.set(identifier, title)
97
- : buffer.set(identifier, ref);
98
- assert(syntax !== 'annotation' || !buffer.has(identifier));
99
- const blank = !!abbr && !content.firstChild;
103
+ const content = frag(ref.firstElementChild!.cloneNode(true).childNodes);
100
104
  const refIndex = ++count;
101
105
  const refId = opts.id !== ''
102
106
  ? `${syntax}:${opts.id ?? ''}:ref:${refIndex}`
@@ -108,24 +112,9 @@ function build(
108
112
  id: opts.id !== '' ? `${syntax}:${opts.id ?? ''}:def:${total + defs.size + 1}` : undefined,
109
113
  'data-marker': !footnote ? marker(total + defs.size + 1, abbr) : undefined,
110
114
  },
111
- [content.cloneNode(true), html('sup')]))
115
+ [contents.get(identifier) ?? frag(), html('sup')]))
112
116
  .get(identifier)!;
113
117
  assert(def.lastChild);
114
- if (title && !blank && def.childNodes.length === 1) {
115
- def.insertBefore(content.cloneNode(true), def.lastChild);
116
- assert(def.childNodes.length > 1);
117
- for (let refs = buffer.take(identifier, Infinity), i = 0; i < refs.length; ++i) {
118
- const ref = refs[i];
119
- if (ref.getAttribute('data-invalid-type') !== 'content') continue;
120
- define(ref, {
121
- title,
122
- class: void ref.classList.remove('invalid'),
123
- 'data-invalid-syntax': null,
124
- 'data-invalid-type': null,
125
- 'data-invalid-message': null,
126
- });
127
- }
128
- }
129
118
  const defIndex = +def.id.slice(def.id.lastIndexOf(':') + 1) || total + defs.size;
130
119
  const defId = def.id || undefined;
131
120
  define(ref, {
@@ -145,9 +134,7 @@ function build(
145
134
  html('a',
146
135
  {
147
136
  href: refId && `#${refId}`,
148
- title: abbr && !blank
149
- ? title
150
- : undefined,
137
+ title: abbr && text(content).trim() || undefined,
151
138
  },
152
139
  `^${refIndex}`));
153
140
  }