securemark 0.280.8 → 0.281.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.280.8",
3
+ "version": "0.281.0",
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",
@@ -28,38 +28,38 @@
28
28
  "LICENSE"
29
29
  ],
30
30
  "dependencies": {
31
- "spica": "0.0.761"
31
+ "spica": "0.0.804"
32
32
  },
33
33
  "devDependencies": {
34
- "@types/dompurify": "3.0.4",
35
- "@types/jquery": "3.5.26",
36
- "@types/mathjax": "0.0.39",
37
- "@types/mocha": "10.0.3",
38
- "@types/power-assert": "1.5.10",
39
- "@types/prismjs": "1.26.2",
40
- "@typescript-eslint/parser": "^6.10.0",
34
+ "@types/dompurify": "3.0.5",
35
+ "@types/jquery": "3.5.30",
36
+ "@types/mathjax": "0.0.40",
37
+ "@types/mocha": "10.0.7",
38
+ "@types/power-assert": "1.5.12",
39
+ "@types/prismjs": "1.26.4",
40
+ "@typescript-eslint/parser": "^8.0.0",
41
41
  "babel-loader": "^9.1.3",
42
42
  "babel-plugin-unassert": "^3.2.0",
43
43
  "concurrently": "^8.2.2",
44
- "eslint": "^8.53.0",
44
+ "eslint": "^9.8.0",
45
45
  "eslint-plugin-redos": "^4.4.5",
46
- "eslint-webpack-plugin": "^4.0.1",
47
- "glob": "^10.3.10",
48
- "karma": "^6.4.2",
46
+ "eslint-webpack-plugin": "^4.2.0",
47
+ "glob": "^11.0.0",
48
+ "karma": "^6.4.4",
49
49
  "karma-chrome-launcher": "^3.2.0",
50
50
  "karma-coverage": "^2.2.1",
51
- "karma-firefox-launcher": "^2.1.2",
51
+ "karma-firefox-launcher": "^2.1.3",
52
52
  "karma-mocha": "^2.0.1",
53
53
  "karma-power-assert": "^1.0.0",
54
- "mocha": "^10.2.0",
55
- "npm-check-updates": "^16.14.6",
56
- "semver": "^7.5.4",
57
- "ts-loader": "^9.5.0",
58
- "typed-dom": "0.0.348",
59
- "typescript": "5.2.2",
60
- "webpack": "^5.89.0",
54
+ "mocha": "^10.7.0",
55
+ "npm-check-updates": "^17.0.2",
56
+ "semver": "^7.6.3",
57
+ "ts-loader": "^9.5.1",
58
+ "typed-dom": "0.0.349",
59
+ "typescript": "5.4.5",
60
+ "webpack": "^5.93.0",
61
61
  "webpack-cli": "^5.1.4",
62
- "webpack-merge": "^5.10.0"
62
+ "webpack-merge": "^6.0.1"
63
63
  },
64
64
  "scripts": {
65
65
  "update": "ncu -u && npm i --no-shrinkwrap && bundle update",
@@ -14,6 +14,7 @@ export class Memo {
14
14
  syntax: number,
15
15
  state: number,
16
16
  ): readonly [any[], number] | readonly [] | undefined {
17
+ assert(position > 0);
17
18
  if (this.count === 0) return;
18
19
  //console.log('get', position, syntax, state, this.memory[position - 1]?.[syntax]?.[state]);
19
20
  const cache = this.memory[position - 1]?.[syntax]?.[state];
@@ -28,6 +29,7 @@ export class Memo {
28
29
  nodes: any[] | undefined,
29
30
  offset: number,
30
31
  ): void {
32
+ assert(position > 0);
31
33
  this.count += +!this.memory[position - 1];
32
34
  const record = this.memory[position - 1] ??= {};
33
35
  assert(!record[syntax]?.[state]);
@@ -1,11 +1,28 @@
1
1
  import { Caches } from '../../..';
2
- import { Cache } from 'spica/cache';
3
- import { Clock } from 'spica/clock';
2
+ import { TClock } from 'spica/tclock';
3
+ import { TLRU } from 'spica/tlru';
4
4
 
5
5
  // For rerendering in editing.
6
6
 
7
+ /*
8
+ 同一文書内で複数回使用される可能性が低いデータ: TClock
9
+ 同一文書内で複数回使用される可能性が高いデータ: TLRU
10
+
11
+ 編集時の再描画高速化が主目的であるためブロックを周期とするループおよび
12
+ 異なるブロックへのジャンプに適したアルゴリズムを使用。
13
+ キャッシュサイズはブロック内の全データをキャッシュできなければならない。
14
+ キャッシュサイズは100あれば足りるが10,000までは速度低下しないようなので
15
+ データサイズを加味して100から1,000とする。
16
+ 遠くで少数の同じデータを高速描画してもあまり意味はない。
17
+ タイムラインとスレッドのmediaにおいても多数の同一データが長周期で複数回表示
18
+ される適切な状況はないと思われる。
19
+ 同一投稿は頻繁に再送されてはならずスパムは削除されなければならず
20
+ ジャーゴンは考慮に値しない。
21
+
22
+ */
23
+
7
24
  export const caches: Caches = {
8
- code: new Clock<string, HTMLElement>(1000),
9
- math: new Cache<string, HTMLElement>(1000),
10
- media: new Clock<string, HTMLElement>(100),
25
+ code: new TClock<string, HTMLElement>(1000),
26
+ math: new TLRU<string, HTMLElement>(1000),
27
+ media: new TClock<string, HTMLElement>(100),
11
28
  } as const;
@@ -34,7 +34,7 @@ describe('Unit: parser/block/heading', () => {
34
34
  it('basic', () => {
35
35
  assert.deepStrictEqual(inspect(parser('# a')), [['<h1 id="index::a">a</h1>'], '']);
36
36
  assert.deepStrictEqual(inspect(parser('# a ')), [['<h1 id="index::a">a</h1>'], '']);
37
- assert.deepStrictEqual(inspect(parser('# a b c \n')), [['<h1 id="index::a_b_c">a b c</h1>'], '']);
37
+ assert.deepStrictEqual(inspect(parser('# a b c \n')), [['<h1 id="index::a_b__c">a b c</h1>'], '']);
38
38
  assert.deepStrictEqual(inspect(parser('# a\n')), [['<h1 id="index::a">a</h1>'], '']);
39
39
  assert.deepStrictEqual(inspect(parser('# *a*`b`${c}$')), [['<h1 id="index::a`b`${c}$"><em>a</em><code data-src="`b`">b</code><span class="math" translate="no" data-src="${c}$">${c}$</span></h1>'], '']);
40
40
  assert.deepStrictEqual(inspect(parser('# a\\')), [['<h1 id="index::a">a</h1>'], '']);
@@ -45,7 +45,7 @@ describe('Unit: parser/inline/extension/index', () => {
45
45
  assert.deepStrictEqual(inspect(parser('[#a ]')), [['<a class="index" href="#index::a">a</a>'], '']);
46
46
  assert.deepStrictEqual(inspect(parser('[#a ]')), [['<a class="index" href="#index::a">a</a>'], '']);
47
47
  assert.deepStrictEqual(inspect(parser('[#a b]')), [['<a class="index" href="#index::a_b">a b</a>'], '']);
48
- assert.deepStrictEqual(inspect(parser('[#a b]')), [['<a class="index" href="#index::a_b">a b</a>'], '']);
48
+ assert.deepStrictEqual(inspect(parser('[#a b]')), [['<a class="index" href="#index::a__b">a b</a>'], '']);
49
49
  assert.deepStrictEqual(inspect(parser('[#a \\ ]')), [['<a class="index" href="#index::a">a</a>'], '']);
50
50
  assert.deepStrictEqual(inspect(parser('[#a &nbsp;]')), [['<a class="index" href="#index::a">a</a>'], '']);
51
51
  assert.deepStrictEqual(inspect(parser('[#a <wbr>]')), [['<a class="index" href="#index::a">a</a>'], '']);
@@ -84,7 +84,7 @@ describe('Unit: parser/inline/extension/index', () => {
84
84
  assert.deepStrictEqual(inspect(parser('[#a|\\b]')), [['<a class="index" href="#index::b">a<span class="indexer" data-index="b"></span></a>'], '']);
85
85
  assert.deepStrictEqual(inspect(parser('[#a|*b*]')), [['<a class="index" href="#index::*b*">a<span class="indexer" data-index="*b*"></span></a>'], '']);
86
86
  assert.deepStrictEqual(inspect(parser('[#a|b c]')), [['<a class="index" href="#index::b_c">a<span class="indexer" data-index="b_c"></span></a>'], '']);
87
- assert.deepStrictEqual(inspect(parser('[#a|b c]')), [['<a class="index" href="#index::b_c">a<span class="indexer" data-index="b_c"></span></a>'], '']);
87
+ assert.deepStrictEqual(inspect(parser('[#a|b c]')), [['<a class="index" href="#index::b__c">a<span class="indexer" data-index="b__c"></span></a>'], '']);
88
88
  assert.deepStrictEqual(inspect(parser('[#a|[]]')), [['<a class="index" href="#index::[]">a<span class="indexer" data-index="[]"></span></a>'], '']);
89
89
  assert.deepStrictEqual(inspect(parser('[#a|&copy;]')), [['<a class="index" href="#index::&amp;copy;">a<span class="indexer" data-index="&amp;copy;"></span></a>'], '']);
90
90
  assert.deepStrictEqual(inspect(parser('[#a |b]')), [['<a class="index" href="#index::b">a<span class="indexer" data-index="b"></span></a>'], '']);
@@ -9,31 +9,59 @@ export function indexee(parser: Parser<HTMLElement, MarkdownParser.Context>, opt
9
9
  return fmap(parser, ([el], _, { id }) => [define(el, { id: identity(id, index(el, optional)) })]);
10
10
  }
11
11
 
12
- export function identity(id: string | undefined, text: string, type: 'index' | 'mark' | '' = 'index'): string | undefined {
12
+ const MAX = 60;
13
+ const ELLIPSIS = '...';
14
+ const PART = (MAX - ELLIPSIS.length) / 2 | 0;
15
+ const REM = MAX - PART * 2 - ELLIPSIS.length;
16
+ export function identity(
17
+ id: string | undefined,
18
+ text: string,
19
+ type: 'index' | 'mark' | '' = 'index',
20
+ ): string | undefined {
13
21
  assert(!id?.match(/[^0-9a-z/-]/i));
14
22
  assert(!text.includes('\n'));
15
23
  if (id === '') return undefined;
16
- text &&= text.trim().replace(/\s\s+/g, ' ');
24
+ text = text.trim();
17
25
  if (text === '') return undefined;
18
- const hash = text.replace(/\s/g, '_');
19
- if (hash.length <= 120 || type === '') return `${type}:${id ?? ''}:${hash}`;
20
- const cs = [...text];
21
- if (cs.length <= 120) return `${type}:${id ?? ''}:${hash}`;
22
- const ellipsis = '...';
23
- const len = (120 - ellipsis.length * 2) / 3 | 0;
26
+ const str = text.replace(/\s/g, '_');
27
+ if (str.length <= MAX || type === '') return `${type}:${id ?? ''}:${str}`;
28
+ const cs = [...str];
29
+ if (cs.length <= MAX) return `${type}:${id ?? ''}:${str}`;
24
30
  switch (type) {
25
31
  case 'index':
26
32
  case 'mark':
27
- const s1 = hash.slice(0, cs.slice(0, len).join('').trimEnd().length);
28
- const s3 = hash.slice(-cs.slice(-len).join('').trimStart().length);
29
- const s2 = cs.slice(cs.length / 2 - len / 2 - (len - s1.length) | 0).slice(0, len + len - s3.length).join('').trim().replace(/\s/g, '_');
30
- return `${type}:${id ?? ''}:${s1}${ellipsis}${s2}${ellipsis}${s3}`;
33
+ const s1 = cs.slice(0, PART + REM).join('');
34
+ const s2 = cs.slice(-PART).join('');
35
+ return `${type}:${id ?? ''}:${s1}${ELLIPSIS}${s2}=${hash(text).toString(36)}`;
31
36
  }
32
37
  assert(false);
33
38
  }
34
- assert(identity(undefined, '0'.repeat(120 - 1) + 1)!.slice(7) === '0'.repeat(120 - 1) + 1);
35
- assert(identity(undefined, '0'.repeat(41) + '1'.repeat(38) + '2'.repeat(41) + 3)!.slice(7) === '0'.repeat(38) + '...' + '1'.repeat(38) + '...' + '2'.repeat(38 - 1) + 3);
36
- assert(identity(undefined, '0'.repeat(81) + '1'.repeat(38) + '2'.repeat(81) + 3)!.slice(7) === '0'.repeat(38) + '...' + '1'.repeat(38) + '...' + '2'.repeat(38 - 1) + 3);
39
+ function hash(source: string): number {
40
+ let x = 1;
41
+ assert(x !== 0);
42
+ for (let i = 0; i < source.length; ++i) {
43
+ x ^= x << 13;
44
+ x ^= x >>> 17;
45
+ x ^= x << 15;
46
+ x ^= source.charCodeAt(i) << 11;
47
+ }
48
+ return x >>> 0;
49
+ }
50
+ assert.deepStrictEqual(
51
+ identity(undefined, `${'0'.repeat(MAX - 1)}1`)!.slice(7),
52
+ `${'0'.repeat(MAX - 1)}1`);
53
+ assert.deepStrictEqual(
54
+ identity(undefined, `0${'1'.repeat(MAX / 2)}${'2'.repeat(MAX / 2)}3`)!.slice(7),
55
+ `0${'1'.repeat(PART + REM - 1)}${ELLIPSIS}${'2'.repeat(PART - 1)}3=x8ujbi`);
56
+ assert.deepStrictEqual(
57
+ identity(undefined, `0${'1'.repeat(MAX * 2)}${'2'.repeat(MAX * 2)}3`)!.slice(7),
58
+ `0${'1'.repeat(PART + REM - 1)}${ELLIPSIS}${'2'.repeat(PART - 1)}3=1c1m3g9`);
59
+ assert.deepStrictEqual(
60
+ identity(undefined, ` ${'0 '.repeat(MAX)}`)!.slice(7),
61
+ identity(undefined, ` ${'0 '.repeat(MAX)}`.trim())!.slice(7));
62
+ assert.notDeepStrictEqual(
63
+ identity(undefined, `${'0 '.repeat(MAX)}`)!.slice(7),
64
+ identity(undefined, `${'0_'.repeat(MAX)}`)!.slice(7));
37
65
 
38
66
  export function index(source: Element, optional = false): string {
39
67
  assert(!source.matches('.indexer'));
@@ -41,7 +69,7 @@ export function index(source: Element, optional = false): string {
41
69
  if (!source.firstChild) return '';
42
70
  const indexer = source.querySelector(':scope > .indexer');
43
71
  const index = indexer?.getAttribute('data-index');
44
- if (index) return index;
72
+ if (index) return index.replace(/=\w+$/, '');
45
73
  if (index === '' && optional) return '';
46
74
  return signature(source);
47
75
  }
@@ -24,7 +24,7 @@ describe('Unit: parser/inline/extension/indexer', () => {
24
24
  assert.deepStrictEqual(inspect(parser(' [|a ]')), [['<span class="indexer" data-index="a"></span>'], '']);
25
25
  assert.deepStrictEqual(inspect(parser(' [|a ]')), [['<span class="indexer" data-index="a"></span>'], '']);
26
26
  assert.deepStrictEqual(inspect(parser(' [|a b]')), [['<span class="indexer" data-index="a_b"></span>'], '']);
27
- assert.deepStrictEqual(inspect(parser(' [|a b]')), [['<span class="indexer" data-index="a_b"></span>'], '']);
27
+ assert.deepStrictEqual(inspect(parser(' [|a b]')), [['<span class="indexer" data-index="a__b"></span>'], '']);
28
28
  assert.deepStrictEqual(inspect(parser(' [|A]')), [['<span class="indexer" data-index="A"></span>'], '']);
29
29
  assert.deepStrictEqual(inspect(parser(' [|*A*]')), [['<span class="indexer" data-index="*A*"></span>'], '']);
30
30
  assert.deepStrictEqual(inspect(parser(' [|`A`]')), [['<span class="indexer" data-index="`A`"></span>'], '']);
@@ -85,7 +85,7 @@ function build(
85
85
  ++iSplitters) {
86
86
  if (~iSplitters << 32 - 8 === 0) yield;
87
87
  if (!scope && el.parentNode !== target) continue;
88
- if (el.tagName === 'OL' && el.nextElementSibling !== splitters[iSplitters + 1]) {
88
+ if (el.tagName === 'OL' && (el.nextElementSibling !== splitters[iSplitters + 1] || defs.size === 0)) {
89
89
  assert(el.matches(`.${syntax}s`));
90
90
  el.remove();
91
91
  continue;