securemark 0.268.0 → 0.268.2

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,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.268.2
4
+
5
+ - Refactoring.
6
+
7
+ ## 0.268.1
8
+
9
+ - Refactoring.
10
+
3
11
  ## 0.268.0
4
12
 
5
13
  - 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.2 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
  /***/ }),
@@ -6118,16 +6143,18 @@ function identity(id, text, name = 'index') {
6118
6143
  if (id === '') return undefined;
6119
6144
  text &&= text.trim().replace(/\s+/g, '_');
6120
6145
  if (text === '') return undefined;
6121
- if (text.length <= 100) return `${name}:${id ?? ''}:${text}`;
6146
+ const cs = [...text];
6147
+ if (cs.length <= 100) return `${name}:${id ?? ''}:${text}`;
6122
6148
  switch (name) {
6123
6149
  case 'index':
6124
- return `${name}:${id ?? ''}:${text.slice(0, 97)}...`;
6150
+ return `${name}:${id ?? ''}:${cs.slice(0, 97).join('')}...`;
6125
6151
  case 'mark':
6126
- return `${name}:${id ?? ''}:${text.slice(0, 50)}...${text.slice(-47)}`;
6152
+ return `${name}:${id ?? ''}:${cs.slice(0, 50).join('')}...${cs.slice(-47).join('')}`;
6127
6153
  }
6128
6154
  }
6129
6155
  exports.identity = identity;
6130
6156
  function text(source, optional = false) {
6157
+ if (!source.firstChild) return '';
6131
6158
  const indexer = source.querySelector(':scope > .indexer');
6132
6159
  const index = indexer?.getAttribute('data-index');
6133
6160
  if (index) return index;
@@ -6959,8 +6986,7 @@ function* figure(target, footnotes, opts = {}) {
6959
6986
  }
6960
6987
  labels.add(label);
6961
6988
  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];
6989
+ for (const ref of refs.take(label, Infinity)) {
6964
6990
  if (ref.getAttribute('data-invalid-message') === messages.reference) {
6965
6991
  (0, dom_1.define)(ref, {
6966
6992
  class: void ref.classList.remove('invalid'),
@@ -7026,7 +7052,6 @@ Object.defineProperty(exports, "__esModule", ({
7026
7052
  }));
7027
7053
  exports.reference = exports.annotation = exports.footnote = void 0;
7028
7054
  const indexee_1 = __webpack_require__(1269);
7029
- const queue_1 = __webpack_require__(4934);
7030
7055
  const dom_1 = __webpack_require__(3252);
7031
7056
  function* footnote(target, footnotes, opts = {}, bottom = null) {
7032
7057
  for (let es = target.querySelectorAll(`.annotations`), len = es.length, i = 0; i < len; ++i) {
@@ -7040,23 +7065,35 @@ function* footnote(target, footnotes, opts = {}, bottom = null) {
7040
7065
  exports.footnote = footnote;
7041
7066
  exports.annotation = build('annotation', n => `*${n}`, 'h1, h2, h3, h4, h5, h6, aside.aside, hr');
7042
7067
  exports.reference = build('reference', (n, abbr) => `[${abbr || n}]`);
7043
- function build(syntax, marker, splitter) {
7068
+ function build(syntax, marker, splitter = '_') {
7044
7069
  // Referenceを含むAnnotationの重複排除は両構文が互いに処理済みであることを必要とするため
7045
7070
  // 構文ごとに各1回の処理では不可能
7046
7071
  return function* (target, footnote, opts = {}, bottom = null) {
7047
7072
  const defs = new Map();
7048
- const buffer = new queue_1.MultiQueue();
7049
- const titles = new Map();
7050
7073
  const splitters = [];
7051
- for (let es = target.querySelectorAll(splitter ?? '_'), len = es.length, i = 0; i < len; ++i) {
7074
+ for (let es = target.querySelectorAll(splitter), len = es.length, i = 0; i < len; ++i) {
7075
+ if (i % 100 === 0) yield;
7052
7076
  const el = es[i];
7053
7077
  el.parentNode === target && splitters.push(el);
7054
7078
  }
7079
+ const refs = target.querySelectorAll(`sup.${syntax}:not(.disabled)`);
7080
+ const titles = new Map();
7081
+ const contents = new Map();
7082
+ for (let len = refs.length, i = 0; i < len; ++i) {
7083
+ if (i % 10 === 9) yield;
7084
+ const ref = refs[i];
7085
+ const identifier = ref.getAttribute('data-abbr') || ` ${ref.firstElementChild.innerHTML}`;
7086
+ if (titles.has(identifier)) continue;
7087
+ const content = (0, dom_1.frag)(ref.firstElementChild.cloneNode(true).childNodes);
7088
+ const title = (0, indexee_1.text)(content).trim();
7089
+ if (!title) continue;
7090
+ titles.set(identifier, title);
7091
+ contents.set(identifier, content);
7092
+ }
7055
7093
  let count = 0;
7056
7094
  let total = 0;
7057
7095
  let style;
7058
- for (let refs = target.querySelectorAll(`sup.${syntax}:not(.disabled)`), len = refs.length, i = 0; i < len; ++i) {
7059
- yield;
7096
+ for (let len = refs.length, i = 0; i < len; ++i) {
7060
7097
  const ref = refs[i];
7061
7098
  while (splitters.length > 0 && splitters[0].compareDocumentPosition(ref) & Node.DOCUMENT_POSITION_FOLLOWING) {
7062
7099
  if (defs.size > 0) {
@@ -7071,7 +7108,6 @@ function build(syntax, marker, splitter) {
7071
7108
  }
7072
7109
  const identifier = ref.getAttribute('data-abbr') || ` ${ref.firstElementChild.innerHTML}`;
7073
7110
  const abbr = ref.getAttribute('data-abbr') || undefined;
7074
- const content = (0, dom_1.frag)(ref.firstElementChild.cloneNode(true).childNodes);
7075
7111
  style ??= abbr ? 'abbr' : 'count';
7076
7112
  if (style === 'count' ? abbr : !abbr) {
7077
7113
  (0, dom_1.define)(ref, {
@@ -7093,29 +7129,14 @@ function build(syntax, marker, splitter) {
7093
7129
  } else {
7094
7130
  ref.lastChild?.remove();
7095
7131
  }
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;
7132
+ const title = titles.get(identifier);
7133
+ const content = (0, dom_1.frag)(ref.firstElementChild.cloneNode(true).childNodes);
7099
7134
  const refIndex = ++count;
7100
7135
  const refId = opts.id !== '' ? `${syntax}:${opts.id ?? ''}:ref:${refIndex}` : undefined;
7101
7136
  const def = false || defs.get(identifier) || defs.set(identifier, (0, dom_1.html)('li', {
7102
7137
  id: opts.id !== '' ? `${syntax}:${opts.id ?? ''}:def:${total + defs.size + 1}` : undefined,
7103
7138
  '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
- }
7139
+ }, [contents.get(identifier) ?? (0, dom_1.frag)(), (0, dom_1.html)('sup')])).get(identifier);
7119
7140
  const defIndex = +def.id.slice(def.id.lastIndexOf(':') + 1) || total + defs.size;
7120
7141
  const defId = def.id || undefined;
7121
7142
  (0, dom_1.define)(ref, {
@@ -7135,7 +7156,7 @@ function build(syntax, marker, splitter) {
7135
7156
  }, marker(defIndex, abbr)));
7136
7157
  def.lastChild.appendChild((0, dom_1.html)('a', {
7137
7158
  href: refId && `#${refId}`,
7138
- title: abbr && !blank ? title : undefined
7159
+ title: abbr && (0, indexee_1.text)(content).trim() || undefined
7139
7160
  }, `^${refIndex}`));
7140
7161
  }
7141
7162
  if (defs.size > 0 || footnote) {
@@ -8368,7 +8389,7 @@ function unlink(h) {
8368
8389
  /***/ 3252:
8369
8390
  /***/ (function(module) {
8370
8391
 
8371
- /*! typed-dom v0.0.315 https://github.com/falsandtru/typed-dom | (c) 2016, falsandtru | (Apache-2.0 AND MPL-2.0) License */
8392
+ /*! typed-dom v0.0.317 https://github.com/falsandtru/typed-dom | (c) 2016, falsandtru | (Apache-2.0 AND MPL-2.0) License */
8372
8393
  (function webpackUniversalModuleDefinition(root, factory) {
8373
8394
  if(true)
8374
8395
  module.exports = factory();
@@ -8699,7 +8720,7 @@ exports.defrag = defrag;
8699
8720
  /***/ 6120:
8700
8721
  /***/ (function(module) {
8701
8722
 
8702
- /*! typed-dom v0.0.315 https://github.com/falsandtru/typed-dom | (c) 2016, falsandtru | (Apache-2.0 AND MPL-2.0) License */
8723
+ /*! typed-dom v0.0.317 https://github.com/falsandtru/typed-dom | (c) 2016, falsandtru | (Apache-2.0 AND MPL-2.0) License */
8703
8724
  (function webpackUniversalModuleDefinition(root, factory) {
8704
8725
  if(true)
8705
8726
  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.2",
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.54.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.10",
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.317",
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
  }
@@ -14,12 +14,13 @@ export function identity(id: string | undefined, text: string, name: 'index' | '
14
14
  if (id === '') return undefined;
15
15
  text &&= text.trim().replace(/\s+/g, '_');
16
16
  if (text === '') return undefined;
17
- if (text.length <= 100) return `${name}:${id ?? ''}:${text}`;
17
+ const cs = [...text];
18
+ if (cs.length <= 100) return `${name}:${id ?? ''}:${text}`;
18
19
  switch (name) {
19
20
  case 'index':
20
- return `${name}:${id ?? ''}:${text.slice(0, 97)}...`;
21
+ return `${name}:${id ?? ''}:${cs.slice(0, 97).join('')}...`;
21
22
  case 'mark':
22
- return `${name}:${id ?? ''}:${text.slice(0, 50)}...${text.slice(-47)}`;
23
+ return `${name}:${id ?? ''}:${cs.slice(0, 50).join('')}...${cs.slice(-47).join('')}`;
23
24
  }
24
25
  assert(false);
25
26
  }
@@ -33,6 +34,7 @@ assert(identity(undefined, '0'.repeat(200) + 1, 'mark')!.slice(6) === '0'.repeat
33
34
  export function text(source: HTMLElement | DocumentFragment, optional = false): string {
34
35
  assert(source instanceof DocumentFragment || !source.matches('.indexer'));
35
36
  assert(source.querySelectorAll(':scope > .indexer').length <= 1);
37
+ if (!source.firstChild) return '';
36
38
  const indexer = source.querySelector(':scope > .indexer');
37
39
  const index = indexer?.getAttribute('data-index');
38
40
  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
  }
package/src/util/info.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { ParseSelector } from 'typed-query-selector/parser';
1
2
  import { Info } from '../..';
2
3
  import { scope } from './scope';
3
4
 
@@ -16,9 +17,10 @@ export function info(source: DocumentFragment | HTMLElement | ShadowRoot): Info
16
17
  media: find('.media[data-src]'),
17
18
  };
18
19
 
19
- function find<T extends HTMLElement>(selector: string): T[] {
20
- const acc: T[] = [];
21
- for (let es = source.querySelectorAll<T>(selector),
20
+ function find<T extends string>(selector: T): ParseSelector<T, HTMLElement>[];
21
+ function find(selector: string): HTMLElement[] {
22
+ const acc = [];
23
+ for (let es = source.querySelectorAll<HTMLElement>(selector),
22
24
  len = es.length, i = 0; i < len; ++i) {
23
25
  const el = es[i];
24
26
  match(el) && acc.push(el);