securemark 0.252.0 → 0.253.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.
Files changed (48) hide show
  1. package/.eslintrc.json +7 -1
  2. package/CHANGELOG.md +12 -0
  3. package/design.md +17 -11
  4. package/dist/index.js +121 -116
  5. package/index.d.ts +7 -8
  6. package/markdown.d.ts +6 -5
  7. package/package.json +9 -9
  8. package/src/combinator/data/parser.ts +1 -1
  9. package/src/parser/api/bind.test.ts +8 -8
  10. package/src/parser/api/bind.ts +1 -1
  11. package/src/parser/api/parse.test.ts +2 -1
  12. package/src/parser/api/parse.ts +0 -1
  13. package/src/parser/block/blockquote.test.ts +31 -31
  14. package/src/parser/block/blockquote.ts +1 -3
  15. package/src/parser/block/extension/aside.test.ts +3 -3
  16. package/src/parser/block/extension/aside.ts +0 -3
  17. package/src/parser/block/extension/example.test.ts +11 -11
  18. package/src/parser/block/extension/example.ts +1 -3
  19. package/src/parser/block/extension/fig.test.ts +5 -5
  20. package/src/parser/block/extension/figure.test.ts +2 -2
  21. package/src/parser/block/extension/message.test.ts +7 -7
  22. package/src/parser/block/extension/message.ts +2 -2
  23. package/src/parser/block/extension/table.ts +6 -2
  24. package/src/parser/block/ilist.ts +4 -5
  25. package/src/parser/block/olist.ts +24 -20
  26. package/src/parser/block/ulist.ts +3 -13
  27. package/src/parser/inline/annotation.test.ts +18 -18
  28. package/src/parser/inline/annotation.ts +1 -1
  29. package/src/parser/inline/autolink/hashnum.ts +1 -1
  30. package/src/parser/inline/autolink/hashtag.ts +5 -5
  31. package/src/parser/inline/autolink.ts +2 -2
  32. package/src/parser/inline/html.test.ts +9 -6
  33. package/src/parser/inline/html.ts +11 -13
  34. package/src/parser/inline/reference.test.ts +58 -58
  35. package/src/parser/inline/reference.ts +1 -1
  36. package/src/parser/inline.test.ts +20 -20
  37. package/src/parser/locale.test.ts +1 -1
  38. package/src/parser/processor/figure.test.ts +3 -3
  39. package/src/parser/processor/figure.ts +3 -6
  40. package/src/parser/processor/footnote.test.ts +60 -2
  41. package/src/parser/processor/footnote.ts +56 -26
  42. package/src/parser/util.ts +4 -4
  43. package/src/renderer/render/code.ts +2 -2
  44. package/src/renderer/render/math.ts +2 -2
  45. package/src/renderer/render/media/image.ts +2 -2
  46. package/src/renderer/render/media.ts +2 -2
  47. package/src/util/info.ts +4 -4
  48. package/src/util/toc.ts +12 -16
@@ -117,8 +117,8 @@ describe('Unit: parser/processor/figure', () => {
117
117
  assert.deepStrictEqual(
118
118
  [...target.children].map(el => el.outerHTML),
119
119
  [
120
- '<blockquote><blockquote><section><figure data-type="quote" data-label="test-a" data-group="test" data-number="1"><figcaption><span class="figindex">Test 1. </span></figcaption><div><blockquote></blockquote></div></figure><ol class="annotations"></ol><ol class="references"></ol></section></blockquote><section><figure data-type="quote" data-label="test-a" data-group="test" data-number="1"><figcaption><span class="figindex">Test 1. </span></figcaption><div><blockquote></blockquote></div></figure><ol class="annotations"></ol><ol class="references"></ol></section></blockquote>',
121
- '<aside class="example" data-type="markdown"><pre translate="no">~~~figure $test-a\n&gt; \n\n~~~\n\n$test-a</pre><hr><section><figure data-type="quote" data-label="test-a" data-group="test" data-number="1"><figcaption><span class="figindex">Test 1. </span></figcaption><div><blockquote></blockquote></div></figure><p><a class="label disabled" data-label="test-a">Test 1</a></p><ol class="annotations"></ol><ol class="references"></ol></section></aside>',
120
+ '<blockquote><blockquote><section><figure data-type="quote" data-label="test-a" data-group="test" data-number="1"><figcaption><span class="figindex">Test 1. </span></figcaption><div><blockquote></blockquote></div></figure><ol class="references"></ol></section></blockquote><section><figure data-type="quote" data-label="test-a" data-group="test" data-number="1"><figcaption><span class="figindex">Test 1. </span></figcaption><div><blockquote></blockquote></div></figure><ol class="references"></ol></section></blockquote>',
121
+ '<aside class="example" data-type="markdown"><pre translate="no">~~~figure $test-a\n&gt; \n\n~~~\n\n$test-a</pre><hr><section><figure data-type="quote" data-label="test-a" data-group="test" data-number="1"><figcaption><span class="figindex">Test 1. </span></figcaption><div><blockquote></blockquote></div></figure><p><a class="label disabled" data-label="test-a">Test 1</a></p><ol class="references"></ol></section></aside>',
122
122
  '<figure data-type="quote" data-label="test-b" data-group="test" data-number="1" id="label:test-b"><figcaption><span class="figindex">Test 1. </span></figcaption><div><blockquote></blockquote></div></figure>',
123
123
  '<figure data-type="quote" data-label="test-a" data-group="test" data-number="2" id="label:test-a"><figcaption><span class="figindex">Test 2. </span></figcaption><div><blockquote></blockquote></div></figure>',
124
124
  ]);
@@ -173,7 +173,7 @@ describe('Unit: parser/processor/figure', () => {
173
173
  '<h2 id="index:0">0</h2>',
174
174
  '<figure data-type="quote" data-label="test-1" data-group="test" data-number="1" id="label:test-1"><figcaption><span class="figindex">Test 1. </span></figcaption><div><blockquote></blockquote></div></figure>',
175
175
  '<h2 id="index:0">0</h2>',
176
- '<blockquote><section><h2>0</h2><ol class="annotations"></ol><ol class="references"></ol></section></blockquote>',
176
+ '<blockquote><section><h2>0</h2><ol class="references"></ol></section></blockquote>',
177
177
  '<figure data-type="quote" data-label="test-b" data-group="test" data-number="2.1" id="label:test-b"><figcaption><span class="figindex">Test 2.1. </span></figcaption><div><blockquote></blockquote></div></figure>',
178
178
  '<h2 id="index:0">0</h2>',
179
179
  '<figure data-label="$-0.0.0" data-group="$" class="invalid" data-invalid-syntax="figure" data-invalid-type="argument" data-invalid-message="Base index must be $-x.0 format"></figure>',
@@ -6,14 +6,11 @@ import { push } from 'spica/array';
6
6
 
7
7
  export function* figure(
8
8
  target: ParentNode & Node,
9
- footnotes?: Readonly<{ annotations: HTMLOListElement; references: HTMLOListElement; }>,
10
- opts: Readonly<{
11
- id?: string;
12
- }> = {},
9
+ footnotes?: { readonly references: HTMLOListElement; },
10
+ opts: { readonly id?: string; } = {},
13
11
  ): Generator<HTMLAnchorElement | undefined, undefined, undefined> {
14
- const refs = new MultiMap<string, HTMLAnchorElement>(push(push(push([],
12
+ const refs = new MultiMap<string, HTMLAnchorElement>(push(push([],
15
13
  target.querySelectorAll('a.label:not(.disabled)[data-label]')),
16
- footnotes?.annotations.querySelectorAll('a.label:not(.disabled)') ?? []),
17
14
  footnotes?.references.querySelectorAll('a.label:not(.disabled)') ?? [])
18
15
  .map(el => [el.getAttribute('data-label')!, el]));
19
16
  const labels = new Set<string>();
@@ -151,8 +151,8 @@ describe('Unit: parser/processor/footnote', () => {
151
151
  assert.deepStrictEqual(
152
152
  [...target.children].map(el => el.outerHTML),
153
153
  [
154
- '<blockquote><blockquote><section><p><sup class="annotation disabled" title="a"><span hidden="">a</span><a>*1</a></sup></p><ol class="annotations"><li>a<sup><a>^1</a></sup></li></ol><ol class="references"></ol></section></blockquote><section><p><sup class="annotation disabled" title="a"><span hidden="">a</span><a>*1</a></sup><br>~~~</p><ol class="annotations"><li>a<sup><a>^1</a></sup></li></ol><ol class="references"></ol></section></blockquote>',
155
- '<aside class="example" data-type="markdown"><pre translate="no">((a))</pre><hr><section><p><sup class="annotation disabled" title="a"><span hidden="">a</span><a>*1</a></sup></p><ol class="annotations"><li>a<sup><a>^1</a></sup></li></ol><ol class="references"></ol></section></aside>',
154
+ '<blockquote><blockquote><section><p><sup class="annotation disabled" title="a"><span hidden="">a</span><a>*1</a></sup></p><ol class="annotations"><li data-marker="*1">a<sup><a>^1</a></sup></li></ol><ol class="references"></ol></section></blockquote><section><p><sup class="annotation disabled" title="a"><span hidden="">a</span><a>*1</a></sup><br>~~~</p><ol class="annotations"><li data-marker="*1">a<sup><a>^1</a></sup></li></ol><ol class="references"></ol></section></blockquote>',
155
+ '<aside class="example" data-type="markdown"><pre translate="no">((a))</pre><hr><section><p><sup class="annotation disabled" title="a"><span hidden="">a</span><a>*1</a></sup></p><ol class="annotations"><li data-marker="*1">a<sup><a>^1</a></sup></li></ol><ol class="references"></ol></section></aside>',
156
156
  '<p><sup class="annotation" id="annotation:ref:1" title="a"><span hidden="">a</span><a href="#annotation:def:1">*1</a></sup></p>',
157
157
  ]);
158
158
  assert.deepStrictEqual(
@@ -187,6 +187,64 @@ describe('Unit: parser/processor/footnote', () => {
187
187
  }
188
188
  });
189
189
 
190
+ it('split', () => {
191
+ const target = parse('((1))\n\n## a\n\n## b\n\n((2))((3))\n\n## c\n\n((4))');
192
+ for (let i = 0; i < 3; ++i) {
193
+ [...annotation(target)];
194
+ assert.deepStrictEqual(
195
+ [...target.children].map(el => el.outerHTML),
196
+ [
197
+ html('p', [
198
+ html('sup', { class: "annotation", id: "annotation:ref:1", title: "1" }, [
199
+ html('span', { hidden: '' }, '1'),
200
+ html('a', { href: "#annotation:def:1" }, '*1')
201
+ ]),
202
+ ]).outerHTML,
203
+ html('ol', { class: 'annotations' }, [
204
+ html('li', { id: 'annotation:def:1', 'data-marker': '*1' }, [
205
+ '1',
206
+ html('sup', [html('a', { href: '#annotation:ref:1' }, '^1')])
207
+ ]),
208
+ ]).outerHTML,
209
+ html('h2', { id: 'index:a' }, 'a').outerHTML,
210
+ html('h2', { id: 'index:b' }, 'b').outerHTML,
211
+ html('p', [
212
+ html('sup', { class: "annotation", id: "annotation:ref:2", title: "2" }, [
213
+ html('span', { hidden: '' }, '2'),
214
+ html('a', { href: "#annotation:def:2" }, '*2')
215
+ ]),
216
+ html('sup', { class: "annotation", id: "annotation:ref:3", title: "3" }, [
217
+ html('span', { hidden: '' }, '3'),
218
+ html('a', { href: "#annotation:def:3" }, '*3')
219
+ ]),
220
+ ]).outerHTML,
221
+ html('ol', { class: 'annotations' }, [
222
+ html('li', { id: 'annotation:def:2', 'data-marker': '*2' }, [
223
+ '2',
224
+ html('sup', [html('a', { href: '#annotation:ref:2' }, '^2')])
225
+ ]),
226
+ html('li', { id: 'annotation:def:3', 'data-marker': '*3' }, [
227
+ '3',
228
+ html('sup', [html('a', { href: '#annotation:ref:3' }, '^3')])
229
+ ]),
230
+ ]).outerHTML,
231
+ html('h2', { id: 'index:c' }, 'c').outerHTML,
232
+ html('p', [
233
+ html('sup', { class: "annotation", id: "annotation:ref:4", title: "4" }, [
234
+ html('span', { hidden: '' }, '4'),
235
+ html('a', { href: "#annotation:def:4" }, '*4')
236
+ ]),
237
+ ]).outerHTML,
238
+ html('ol', { class: 'annotations' }, [
239
+ html('li', { id: 'annotation:def:4', 'data-marker': '*4' }, [
240
+ '4',
241
+ html('sup', [html('a', { href: '#annotation:ref:4' }, '^4')])
242
+ ]),
243
+ ]).outerHTML,
244
+ ]);
245
+ }
246
+ });
247
+
190
248
  });
191
249
 
192
250
  describe('reference', () => {
@@ -1,55 +1,65 @@
1
- import { undefined, Infinity, Map, WeakMap } from 'spica/global';
1
+ import { undefined, Infinity, Map, Node } from 'spica/global';
2
2
  import { text } from '../inline/extension/indexee';
3
3
  import { frag, html, define } from 'typed-dom/dom';
4
4
  import { MultiMap } from 'spica/multimap';
5
- import { memoize } from 'spica/memoize';
5
+ import { push } from 'spica/array';
6
6
 
7
7
  export function* footnote(
8
8
  target: ParentNode & Node,
9
- footnotes?: Readonly<{ annotations: HTMLOListElement; references: HTMLOListElement; }>,
10
- opts: Readonly<{ id?: string; }> = {},
9
+ footnotes?: { readonly annotations?: HTMLOListElement; readonly references: HTMLOListElement; },
10
+ opts: { readonly id?: string; } = {},
11
+ bottom: Node | null = null,
11
12
  ): Generator<HTMLAnchorElement | HTMLLIElement | undefined, undefined, undefined> {
12
- yield* reference(target, footnotes?.references, opts, footnotes?.annotations && [footnotes.annotations]);
13
- yield* annotation(target, footnotes?.annotations, opts, []);
13
+ yield* reference(target, footnotes?.references, opts, bottom);
14
+ yield* annotation(target, footnotes?.annotations, opts, bottom);
14
15
  return;
15
16
  }
16
17
 
17
- export const annotation = build('annotation', n => `*${n}`);
18
+ export const annotation = build('annotation', n => `*${n}`, 'h1, h2, h3, h4, h5, h6, aside.aside, hr');
18
19
  export const reference = build('reference', (n, abbr) => `[${abbr || n}]`);
19
20
 
20
21
  function build(
21
- syntax: string,
22
+ syntax: 'annotation' | 'reference',
22
23
  marker: (index: number, abbr: string | undefined) => string,
24
+ splitter?: string,
23
25
  ) {
24
26
  assert(syntax.match(/^[a-z]+$/));
25
27
  // Referenceを含むAnnotationの重複排除は両構文が互いに処理済みであることを必要とするため
26
28
  // 構文ごとに各1回の処理では不可能
27
- const identify = memoize<HTMLElement, string>(
28
- ref => `${+!ref.querySelector('.label')}:${ref.getAttribute('data-abbr') || '_' + ref.innerHTML}`,
29
- new WeakMap());
30
- const contentify = memoize<HTMLElement, DocumentFragment>(
31
- ref => frag(ref.cloneNode(true).childNodes),
32
- new WeakMap());
33
29
  return function* (
34
30
  target: ParentNode & Node,
35
31
  footnote?: HTMLOListElement,
36
- opts: Readonly<{ id?: string }> = {},
37
- footnotes: readonly HTMLOListElement[] = [],
32
+ opts: { readonly id?: string } = {},
33
+ bottom: Node | null = null,
38
34
  ): Generator<HTMLAnchorElement | HTMLLIElement | undefined, undefined, undefined> {
39
35
  const defs = new Map<string, HTMLLIElement>();
40
36
  const buffer = new MultiMap<string, HTMLElement>();
41
37
  const titles = new Map<string, string>();
42
- const check = footnotes.some(el => target.contains(el));
38
+ // Bug: Firefox
39
+ //const splitters = push([], target.querySelectorAll(`:scope > :is(${splitter ?? '_'})`));
40
+ const splitters = push([], target.querySelectorAll(splitter ?? '_'))
41
+ .filter(el => el.parentNode === target);
42
+ // Bug: Firefox
43
+ //target.querySelectorAll(`:scope > .${syntax}s`).forEach(el => el.remove());
44
+ target.querySelectorAll(`.${syntax}s`).forEach(el => el.parentNode === target && el.remove());
45
+ let offset = 0;
43
46
  let style: 'count' | 'abbr';
44
47
  for (
45
48
  let refs = target.querySelectorAll(`sup.${syntax}:not(.disabled)`),
46
49
  i = 0, len = refs.length; i < len; ++i) {
47
50
  yield;
48
51
  const ref = refs[i];
49
- if (check && footnotes.some(el => el.contains(ref))) continue;
50
- const identifier = identify(ref);
52
+ while (+splitters[0]?.compareDocumentPosition(ref) & Node.DOCUMENT_POSITION_FOLLOWING) {
53
+ if (defs.size > 0) {
54
+ offset += defs.size;
55
+ yield* proc(defs, target.insertBefore(html('ol', { class: `${syntax}s` }), splitters[0] ?? null));
56
+ }
57
+ splitters.shift();
58
+ }
59
+ if (syntax === 'annotation' && ref.closest('#annotations, .annotations, #references, .references')) continue;
60
+ const identifier = `${+!ref.querySelector('.label')}:${ref.getAttribute('data-abbr') || '_' + ref.firstElementChild!.innerHTML}`;
51
61
  const abbr = ref.getAttribute('data-abbr') || undefined;
52
- const content = contentify(ref);
62
+ const content = frag(ref.firstElementChild!.cloneNode(true).childNodes);
53
63
  style ??= abbr ? 'abbr' : 'count';
54
64
  if (style === 'count' ? abbr : !abbr) {
55
65
  define(ref, {
@@ -59,8 +69,16 @@ function build(
59
69
  'data-invalid-message': `${syntax[0].toUpperCase() + syntax.slice(1)} style must be consistent`,
60
70
  });
61
71
  }
62
- if (ref.firstElementChild?.getAttribute('hidden') !== '') {
63
- ref.replaceChildren(html('span', { hidden: '' }, ref.childNodes));
72
+ else if (ref.getAttribute('data-invalid-type') === 'style') {
73
+ define(ref, {
74
+ class: void ref.classList.remove('invalid'),
75
+ 'data-invalid-syntax': null,
76
+ 'data-invalid-type': null,
77
+ 'data-invalid-message': null,
78
+ });
79
+ }
80
+ if (!ref.firstElementChild!.hasAttribute('hidden')) {
81
+ ref.firstElementChild!.setAttribute('hidden', '');
64
82
  }
65
83
  else {
66
84
  ref.lastChild?.remove();
@@ -71,9 +89,11 @@ function build(
71
89
  || text(content).trim()
72
90
  || content.textContent!.trim()
73
91
  || undefined;
92
+ assert(syntax !== 'annotation' || title);
74
93
  title
75
94
  ? !titles.has(identifier) && titles.set(identifier, title)
76
95
  : buffer.set(identifier, ref);
96
+ assert(syntax !== 'annotation' || !buffer.has(identifier));
77
97
  const blank = !!abbr && !content.firstChild;
78
98
  const refIndex = i + 1;
79
99
  const refId = opts.id !== ''
@@ -82,7 +102,10 @@ function build(
82
102
  const def = undefined
83
103
  || defs.get(identifier)
84
104
  || defs.set(identifier, html('li',
85
- { id: opts.id !== '' ? `${syntax}:${opts.id ? `${opts.id}:` : ''}def:${defs.size + 1}` : undefined },
105
+ {
106
+ id: opts.id !== '' ? `${syntax}:${opts.id ? `${opts.id}:` : ''}def:${defs.size + offset + 1}` : undefined,
107
+ 'data-marker': !footnote ? marker(defs.size + offset + 1, abbr) : undefined,
108
+ },
86
109
  [content.cloneNode(true), html('sup')]))
87
110
  .get(identifier)!;
88
111
  assert(def.lastChild);
@@ -100,7 +123,7 @@ function build(
100
123
  });
101
124
  }
102
125
  }
103
- const defIndex = +def.id.slice(def.id.lastIndexOf(':') + 1) || defs.size;
126
+ const defIndex = +def.id.slice(def.id.lastIndexOf(':') + 1) || defs.size + offset;
104
127
  const defId = def.id || undefined;
105
128
  define(ref, {
106
129
  id: refId,
@@ -125,13 +148,20 @@ function build(
125
148
  },
126
149
  `^${refIndex}`));
127
150
  }
128
- if (!footnote) return;
151
+ if (defs.size > 0 || footnote) {
152
+ yield* proc(defs, footnote ?? target.insertBefore(html('ol', { class: `${syntax}s` }), splitters[0] ?? bottom));
153
+ }
154
+ return;
155
+ }
156
+
157
+ function* proc(defs: Map<string, HTMLLIElement>, footnote: HTMLOListElement): Generator<HTMLLIElement | undefined, undefined, undefined> {
129
158
  const { children } = footnote;
130
159
  const size = defs.size;
131
160
  let count = 0;
132
161
  let length = children.length;
133
162
  I:
134
- for (const def of defs.values()) {
163
+ for (const [key, def] of defs) {
164
+ defs.delete(key);
135
165
  ++count;
136
166
  while (length > size) {
137
167
  const node = children[count - 1] as HTMLLIElement;
@@ -8,8 +8,8 @@ import { invisibleHTMLEntityNames } from './api/normalize';
8
8
  import { reduce } from 'spica/memoize';
9
9
  import { push } from 'spica/array';
10
10
 
11
- export const regBlankStart = new RegExp(String.raw
12
- `^(?:\\?[^\S\n]|&(?:${invisibleHTMLEntityNames.join('|')});|<wbr>)+`);
11
+ export const regBlankStart = new RegExp(
12
+ /^(?:\\?[^\S\n]|&IHN;|<wbr>)+/.source.replace('IHN', `(?:${invisibleHTMLEntityNames.join('|')})`));
13
13
 
14
14
  export function blankWith(delimiter: string | RegExp): RegExp;
15
15
  export function blankWith(starting: '' | '\n', delimiter: string | RegExp): RegExp;
@@ -25,8 +25,8 @@ export function blankWith(starting: '' | '\n', delimiter?: string | RegExp): Reg
25
25
 
26
26
  export function visualize<P extends Parser<HTMLElement | string>>(parser: P): P;
27
27
  export function visualize<T extends HTMLElement | string>(parser: Parser<T>): Parser<T> {
28
- const blankline = new RegExp(String.raw
29
- `^(?:\\$|\\?[^\S\n]|&(?:${invisibleHTMLEntityNames.join('|')});|<wbr>)+$`,
28
+ const blankline = new RegExp(
29
+ /^(?:\\$|\\?[^\S\n]|&IHN;|<wbr>)+$/.source.replace('IHN', `(?:${invisibleHTMLEntityNames.join('|')})`),
30
30
  'gm');
31
31
  return union([
32
32
  convert(
@@ -1,7 +1,7 @@
1
1
  import Prism from 'prismjs';
2
- import { Collection } from 'spica/collection';
2
+ import { Dict } from 'spica/dict';
3
3
 
4
- export function code(target: HTMLElement, cache?: Collection<string, HTMLElement>): void {
4
+ export function code(target: HTMLElement, cache?: Dict<string, HTMLElement>): void {
5
5
  assert(target.children.length === 0);
6
6
  const source = target.textContent!;
7
7
  Prism.highlightElement(target, false, () =>
@@ -1,8 +1,8 @@
1
1
  import { undefined } from 'spica/global';
2
- import { Collection } from 'spica/collection';
2
+ import { Dict } from 'spica/dict';
3
3
  import { html, define } from 'typed-dom/dom';
4
4
 
5
- export function math(target: HTMLElement, cache?: Collection<string, HTMLElement>): void {
5
+ export function math(target: HTMLElement, cache?: Dict<string, HTMLElement>): void {
6
6
  assert(target.children.length === 0);
7
7
  const source = target.textContent!;
8
8
  queue(target, () => {
@@ -1,8 +1,8 @@
1
1
  import { Object } from 'spica/global';
2
- import { Collection } from 'spica/collection';
2
+ import { Dict } from 'spica/dict';
3
3
  import { define } from 'typed-dom/dom';
4
4
 
5
- export function image(source: HTMLImageElement, url: URL, cache?: Collection<string, HTMLElement>): HTMLImageElement {
5
+ export function image(source: HTMLImageElement, url: URL, cache?: Dict<string, HTMLElement>): HTMLImageElement {
6
6
  if (cache?.has(url.href)) return define(
7
7
  cache.get(url.href)!.cloneNode(true) as HTMLImageElement,
8
8
  Object.fromEntries([...source.attributes]
@@ -5,7 +5,7 @@ import { pdf } from './media/pdf';
5
5
  import { video } from './media/video';
6
6
  import { audio } from './media/audio';
7
7
  import { image } from './media/image';
8
- import { Collection } from 'spica/collection';
8
+ import { Dict } from 'spica/dict';
9
9
  import { ReadonlyURL } from 'spica/url';
10
10
  import { reduce } from 'spica/memoize';
11
11
 
@@ -14,7 +14,7 @@ type MediaOptions = NonNullable<RenderingOptions['media']>;
14
14
  const extend = reduce((opts: MediaOptions): MediaOptions =>
15
15
  ({ twitter, youtube, pdf, video, audio, image, ...opts }));
16
16
 
17
- export function media(base: string, source: HTMLImageElement, opts: MediaOptions, cache?: Collection<string, HTMLElement>): HTMLElement | undefined {
17
+ export function media(base: string, source: HTMLImageElement, opts: MediaOptions, cache?: Dict<string, HTMLElement>): HTMLElement | undefined {
18
18
  assert(source.matches('img:not([src])[data-src]'));
19
19
  opts = extend(opts);
20
20
  const url = new ReadonlyURL(source.getAttribute('data-src')!, base);
package/src/util/info.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import { Info } from '../..';
2
2
  import { scope } from './scope';
3
- import { querySelectorAll } from 'typed-dom/query';
3
+ import { push } from 'spica/array';
4
4
 
5
5
  export function info(source: DocumentFragment | HTMLElement | ShadowRoot): Info {
6
6
  const match = scope(source, '.invalid');
7
7
  return {
8
- url: find<HTMLAnchorElement>('a:not(.email):not(.account):not(.channel):not(.hashtag):not(.hashnum):not(.anchor)')
8
+ url: find<HTMLAnchorElement>('a:not(:is(.email, .account, .channel, .hashtag, .hashnum, .anchor))')
9
9
  .filter(el => ['http:', 'https:'].includes(el.protocol)),
10
- tel: find<HTMLAnchorElement>('a:not(.email):not(.account):not(.channel):not(.hashtag):not(.hashnum):not(.anchor)')
10
+ tel: find<HTMLAnchorElement>('a:not(:is(.email, .account, .channel, .hashtag, .hashnum, .anchor))')
11
11
  .filter(el => ['tel:'].includes(el.protocol)),
12
12
  email: find('a.email'),
13
13
  account: find('a.account'),
@@ -20,7 +20,7 @@ export function info(source: DocumentFragment | HTMLElement | ShadowRoot): Info
20
20
  };
21
21
 
22
22
  function find<T extends HTMLElement>(selector: string): T[] {
23
- return querySelectorAll<T>(source, selector)
23
+ return push([], source.querySelectorAll<T>(selector))
24
24
  .filter(match);
25
25
  }
26
26
  }
package/src/util/toc.ts CHANGED
@@ -1,26 +1,22 @@
1
- import { undefined, Array } from 'spica/global';
1
+ import { undefined } from 'spica/global';
2
2
  import { html } from 'typed-dom/dom';
3
3
  import { push } from 'spica/array';
4
4
 
5
5
  // Bug: Firefox
6
6
  //const selector = 'h1 h2 h3 h4 h5 h6 aside.aside'.split(' ').map(s => `:scope > ${s}[id]`).join();
7
- const selector = 'h1 h2 h3 h4 h5 h6 aside.aside'.split(' ').map(s => `${s}[id]`).join();
7
+ const selector = ':is(h1, h2, h3, h4, h5, h6, aside.aside)[id]';
8
8
 
9
9
  export function toc(source: DocumentFragment | HTMLElement | ShadowRoot): HTMLUListElement {
10
- const es = source.querySelectorAll(selector);
11
- const hs: HTMLHeadingElement[] = Array(es.length);
12
- for (let i = 0; i < hs.length; ++i) {
13
- const el = es[i];
14
- assert(el.parentNode === source);
15
- switch (el.tagName) {
16
- case 'ASIDE':
17
- hs[i] = html(el.firstElementChild!.tagName.toLowerCase() as 'h1', { id: el.id, class: 'aside' }, el.firstElementChild!.cloneNode(true).childNodes);
18
- continue;
19
- default:
20
- hs[i] = el as HTMLHeadingElement;
21
- continue;
22
- }
23
- }
10
+ const hs = push([], source.querySelectorAll(selector))
11
+ .map(el => {
12
+ assert(el.parentNode === source);
13
+ switch (el.tagName) {
14
+ case 'ASIDE':
15
+ return html(el.firstElementChild!.tagName.toLowerCase() as 'h1', { id: el.id, class: 'aside' }, el.firstElementChild!.cloneNode(true).childNodes);
16
+ default:
17
+ return el as HTMLHeadingElement;
18
+ }
19
+ });
24
20
  return parse(cons(hs));
25
21
  }
26
22