securemark 0.244.2 → 0.247.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/global.test.d.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  import assert from 'power-assert';
2
2
 
3
- type Assert = typeof assert;
3
+ declare namespace NS {
4
+ export {
5
+ assert,
6
+ }
7
+ }
4
8
 
5
9
  declare global {
6
- const assert: Assert;
10
+ const assert: typeof NS.assert;
7
11
  }
package/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ import 'spica/global';
2
+ export * from './src/parser';
3
+ export * from './src/util';
4
+ export * from './src/renderer';
package/karma.conf.js CHANGED
@@ -1,48 +1,29 @@
1
1
  module.exports = function (config) {
2
2
  config.set({
3
- basePath: '',
4
- frameworks: ['mocha'],
3
+ browsers: ['Chrome', 'Firefox'],
4
+ frameworks: ['mocha', 'power-assert'],
5
5
  files: [
6
- { pattern: 'https://cdn.polyfill.io/v3/polyfill.js?flags=gated&features=default', watched: false, served: false, included: true },
7
- { pattern: 'https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js', watched: false, served: false, included: true },
8
- { pattern: 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.22.0/prism.js', watched: false, served: false, included: true },
9
- { pattern: 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.22.0/plugins/autoloader/prism-autoloader.min.js', watched: false, served: false, included: true },
10
- { pattern: 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.0/es5/tex-chtml.min.js', watched: false, served: false, included: true },
11
- { pattern: 'https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.2.2/purify.js', watched: false, served: false, included: true },
12
- { pattern: 'node_modules/power-assert/build/power-assert.js', watched: true, served: true, included: true },
13
- { pattern: 'dist/*.test.js', watched: true, served: true, included: true }
6
+ { pattern: 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.js', watched: false, served: false, included: true, integrity: 'sha512-2iwCHjuj+PmdCyvb88rMOch0UcKQxVHi/gsAml1fN3eg82IDaO/cdzzeXX4iF2VzIIes7pODE1/G0ts3QBwslA==' },
7
+ { pattern: 'https://cdnjs.cloudflare.com/ajax/libs/benchmark/2.1.4/benchmark.js', watched: false, served: false, included: true, integrity: 'sha512-XnVGk21Ij51MbU8XezQpkwZ1/GA8b5qmoVGIOdJLBYycutjkaeemipzRJP7P6mEJl99OfnweA7M3e4WLfuG7Aw==' },
8
+ { pattern: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js', watched: false, served: false, included: true, integrity: 'sha512-n/4gHW3atM3QqRcbCn6ewmpxcLAHGaDjpEBu4xZd47N0W2oQ+6q7oc3PXstrJYXcbNU1OHdQ1T7pAP+gi5Yu8g==' },
9
+ { pattern: 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/prism.min.js', watched: false, served: false, included: true, integrity: 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.22.0/prism.js' },
10
+ { pattern: 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/plugins/autoloader/prism-autoloader.min.js', watched: false, served: false, included: true, integrity: 'sha512-fTl/qcO1VgvKtOMApX2PdZzkziyr2stM65GYPLGuYMnuMm1z2JLJG6XVU7C/mR+E7xBUqCivykuhlzfqxXBXbg==' },
11
+ { pattern: 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.0/es5/tex-chtml.min.js', watched: false, served: false, included: true, integrity: 'sha512-93xLZnNMlYI6xaQPf/cSdXoBZ23DThX7VehiGJJXB76HTTalQKPC5CIHuFX8dlQ5yzt6baBQRJ4sDXhzpojRJA==' },
12
+ { pattern: 'https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.3.8/purify.js', watched: false, served: false, included: true, integrity: 'sha512-QaF+0tDlqVmwZaQSc0kImgYmw+Cd66TxA5D9X70I5V9BNSqk6yBTbyqw2VEUsVYV5OTbxw8HD9d45on1wvYv7g==' },
13
+ { pattern: 'dist/**/*.{js,map}', watched: true, served: true, included: true },
14
14
  ],
15
- exclude: [
16
- ],
17
- espowerPreprocessor: {
18
- options: {
19
- emitActualCode: false,
20
- ignoreUpstreamSourceMap: true
21
- }
15
+ reporters: ['dots', 'coverage'],
16
+ preprocessors: {
17
+ 'dist/**/*.js': ['coverage'],
22
18
  },
23
- reporters: ['dots'],
24
- coverageIstanbulReporter: {
25
- reports: ['html', 'lcovonly', 'text-summary'],
19
+ coverageReporter: {
26
20
  dir: 'coverage',
27
- combineBrowserReports: true,
28
- skipFilesWithNoCoverage: false,
29
- verbose: false,
30
- 'report-config': {
31
- html: {
32
- subdir: 'html',
33
- },
34
- },
35
- instrumentation: {
36
- 'default-excludes': false,
37
- },
38
- },
39
- coverageIstanbulInstrumenter: {
40
- esModules: true,
21
+ reporters: [
22
+ { type: 'html', subdir: browser => browser.split(/\s/)[0] },
23
+ { type: 'text-summary', subdir: '.', file: 'summary.txt' },
24
+ ],
41
25
  },
42
- autoWatch: true,
43
- autoWatchBatchDelay: 500,
44
- browserDisconnectTimeout: 30000,
45
- browsers: ['Chrome'],
46
- singleRun: true,
26
+ browserDisconnectTimeout: 60 * 1e3,
27
+ browserNoActivityTimeout: 90 * 1e3,
47
28
  });
48
29
  };
package/markdown.d.ts CHANGED
@@ -652,6 +652,7 @@ export namespace MarkdownParser {
652
652
  InlineParser.ReferenceParser,
653
653
  InlineParser.TemplateParser,
654
654
  InlineParser.CommentParser,
655
+ InlineParser.MathParser,
655
656
  InlineParser.ExtensionParser,
656
657
  InlineParser.RubyParser,
657
658
  InlineParser.LinkParser,
@@ -664,7 +665,6 @@ export namespace MarkdownParser {
664
665
  InlineParser.StrongParser,
665
666
  InlineParser.EmphasisParser,
666
667
  InlineParser.CodeParser,
667
- InlineParser.MathParser,
668
668
  InlineParser.HTMLEntityParser,
669
669
  InlineParser.ShortmediaParser,
670
670
  InlineParser.AutolinkParser,
@@ -748,6 +748,36 @@ export namespace MarkdownParser {
748
748
  InlineParser,
749
749
  ]> {
750
750
  }
751
+ export interface MathParser extends
752
+ // $expr$
753
+ // ${expr}$
754
+ Inline<'math'>,
755
+ Parser<HTMLElement, Context, [
756
+ MathParser.BracketParser,
757
+ Parser<string, Context, [
758
+ MathParser.BracketParser,
759
+ MathParser.QuoteParser,
760
+ SourceParser.StrParser,
761
+ ]>,
762
+ ]> {
763
+ }
764
+ export namespace MathParser {
765
+ export interface BracketParser extends
766
+ Inline<'math/bracket'>,
767
+ Parser<HTMLElement, Context, [
768
+ BracketParser,
769
+ SourceParser.EscapableSourceParser,
770
+ ]> {
771
+ }
772
+ export interface QuoteParser extends
773
+ Inline<'math/quote'>,
774
+ Parser<HTMLElement, Context, [
775
+ QuoteParser,
776
+ BracketParser,
777
+ SourceParser.StrParser,
778
+ ]> {
779
+ }
780
+ }
751
781
  export interface ExtensionParser extends
752
782
  // [#abc]
753
783
  Inline<'extension'>,
@@ -1039,24 +1069,6 @@ export namespace MarkdownParser {
1039
1069
  SourceParser.StrParser,
1040
1070
  ]> {
1041
1071
  }
1042
- export interface MathParser extends
1043
- // $expr$
1044
- // ${expr}$
1045
- Inline<'math'>,
1046
- Parser<HTMLElement, Context, [
1047
- SourceParser.StrParser,
1048
- MathParser.BracketParser,
1049
- ]> {
1050
- }
1051
- export namespace MathParser {
1052
- export interface BracketParser extends
1053
- Inline<'math/bracket'>,
1054
- Parser<HTMLElement, Context, [
1055
- BracketParser,
1056
- SourceParser.EscapableSourceParser,
1057
- ]> {
1058
- }
1059
- }
1060
1072
  export interface HTMLEntityParser extends
1061
1073
  // &copy;
1062
1074
  Inline<'htmlentity'>,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.244.2",
3
+ "version": "0.247.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",
@@ -13,22 +13,20 @@
13
13
  "latex"
14
14
  ],
15
15
  "types": "./index.d.ts",
16
- "main": "./dist/securemark.js",
16
+ "main": "./dist/index.js",
17
17
  "files": [
18
18
  "dist",
19
- "*.d.ts",
19
+ "index.*",
20
20
  "src",
21
21
  "test",
22
+ "*.ts",
22
23
  "*.js",
23
24
  "*.json",
25
+ "*.map",
24
26
  "*.md",
25
27
  "NOTICE",
26
28
  "LICENSE"
27
29
  ],
28
- "browserify-shim": {
29
- "prismjs": "global:Prism",
30
- "dompurify": "global:DOMPurify"
31
- },
32
30
  "devDependencies": {
33
31
  "@types/dompurify": "2.3.3",
34
32
  "@types/jquery": "3.5.14",
@@ -36,41 +34,38 @@
36
34
  "@types/mocha": "9.1.1",
37
35
  "@types/power-assert": "1.5.8",
38
36
  "@types/prismjs": "1.26.0",
39
- "browserify": "^17.0.0",
40
- "browserify-shim": "^3.8.14",
41
- "concurrently": "^7.1.0",
42
- "del": "^6.0.0",
43
- "eslint-plugin-redos": "^4.3.0",
44
- "gulp": "^4.0.2",
45
- "gulp-derequire": "^3.0.0",
46
- "gulp-eslint": "^6.0.0",
47
- "gulp-footer": "^2.1.0",
48
- "gulp-header": "^2.0.9",
49
- "gulp-load-plugins": "^2.0.7",
50
- "gulp-mocha": "^8.0.0",
51
- "gulp-rename": "^2.0.0",
52
- "gulp-unassert": "^2.0.0",
53
- "karma": "^6.3.19",
37
+ "@typescript-eslint/parser": "^5.25.0",
38
+ "babel-loader": "^8.2.5",
39
+ "babel-plugin-unassert": "^3.2.0",
40
+ "concurrently": "^7.2.0",
41
+ "eslint": "^8.15.0",
42
+ "eslint-plugin-redos": "^4.4.0",
43
+ "eslint-webpack-plugin": "^3.1.1",
44
+ "glob": "^8.0.3",
45
+ "karma": "^6.3.20",
54
46
  "karma-chrome-launcher": "^3.1.1",
55
- "karma-coverage-istanbul-instrumenter": "^1.0.4",
56
- "karma-coverage-istanbul-reporter": "^3.0.3",
57
- "karma-espower-preprocessor": "^1.2.0",
47
+ "karma-coverage": "^2.2.0",
58
48
  "karma-firefox-launcher": "^2.1.2",
59
49
  "karma-mocha": "^2.0.1",
50
+ "karma-power-assert": "^1.0.0",
60
51
  "mocha": "^10.0.0",
61
- "npm-check-updates": "^12.5.11",
62
- "power-assert": "^1.6.1",
52
+ "npm-check-updates": "^13.0.1",
63
53
  "semver": "^7.3.7",
64
- "spica": "0.0.540",
65
- "tsify": "^5.0.4",
66
- "typed-dom": "0.0.272",
67
- "typed-query-selector": "^2.6.1",
54
+ "spica": "0.0.551",
55
+ "ts-loader": "^9.3.0",
56
+ "typed-dom": "^0.0.295",
68
57
  "typescript": "4.6.4",
69
- "vinyl-buffer": "^1.0.1",
70
- "vinyl-source-stream": "^2.0.0"
58
+ "webpack": "^5.72.1",
59
+ "webpack-cli": "^4.9.2",
60
+ "webpack-merge": "^5.8.0"
71
61
  },
72
62
  "scripts": {
73
- "test": "gulp test"
63
+ "update": "ncu -u && npm i --no-shrinkwrap && bundle update",
64
+ "dev": "concurrently \"webpack --env mode=test -w\" \"karma start --auto-watch --reporters dots\" \"webpack --env mode=dist -w --output-path ./gh-pages/assets/dist\" \"bundle exec jekyll serve -s ./gh-pages -d ./gh-pages/_site --incremental\"",
65
+ "test": "webpack --env mode=test && karma start --single-run --concurrency 1",
66
+ "lint": "webpack --env mode=lint",
67
+ "bench": "webpack --env mode=bench && karma start --single-run --concurrency 1 --reporters dots",
68
+ "dist": "webpack --env mode=dist"
74
69
  },
75
70
  "author": "falsandtru",
76
71
  "license": "UNLICENSED"
@@ -10,20 +10,6 @@ describe('Unit: parser/block/extension/table', () => {
10
10
  it('invalid', () => {
11
11
  assert.deepStrictEqual(inspect(parser('~~~table a\n-\n~~~')), [['<pre class="invalid" translate="no">~~~table a\n-\n~~~</pre>'], '']);
12
12
  assert.deepStrictEqual(inspect(parser(`~~~table\n0${'\n'.repeat(10001)}~~~`), '>'), [['<pre class="invalid" translate="no">'], '']);
13
- assert.deepStrictEqual(
14
- inspect(parser([
15
- '~~~table',
16
- `${[...Array(33)].map((_, i) => `${i + 1}`).join('\n')}`,
17
- '~~~'
18
- ].join('\n'))),
19
- [[`<pre class="invalid" translate="no">~~~table\n${[...Array(33)].map((_, i) => `${i + 1}`).join('\n')}\n~~~</pre>`], '']);
20
- assert.deepStrictEqual(
21
- inspect(parser([
22
- '~~~table',
23
- '::33 1',
24
- '~~~'
25
- ].join('\n'))),
26
- [[`<pre class="invalid" translate="no">~~~table\n::33 1\n~~~</pre>`], '']);
27
13
  });
28
14
 
29
15
  it('data', () => {
@@ -483,8 +469,8 @@ describe('Unit: parser/block/extension/table', () => {
483
469
  assert.deepStrictEqual(
484
470
  inspect(parser([
485
471
  '~~~table',
486
- `-\n# 1\n${[...Array(31)].map((_, i) => `: ${i + 2}`).join('\n')}`,
487
- `-\n#! 1\n${[...Array(31)].map((_, i) => `: ${i + 2}`).join('\n')}`,
472
+ `-\n# 1\n${[...Array(32)].map((_, i) => `: ${i + 2}`).join('\n')}`,
473
+ `-\n#! 1\n${[...Array(32)].map((_, i) => `: ${i + 2}`).join('\n')}`,
488
474
  '~~~'
489
475
  ].join('\n'))),
490
476
  [[html('table', [
@@ -492,52 +478,52 @@ describe('Unit: parser/block/extension/table', () => {
492
478
  html('tbody', [
493
479
  html('tr', [
494
480
  html('th', '1'),
495
- ...[...Array(31)].map((_, i) => html('td', `${i + 2}`)),
481
+ ...[...Array(32)].map((_, i) => html('td', `${i + 2}`)),
496
482
  ]),
497
483
  html('tr', [
498
484
  html('th', { class: 'highlight' }, '1'),
499
- ...[...Array(31)].map((_, i) => html('td', { class: 'highlight', highlight: 'h' }, `${i + 2}`)),
485
+ ...[...Array(32)].map((_, i) => html('td', { class: 'highlight', highlight: 'h' }, `${i + 2}`)),
500
486
  ]),
501
487
  ]),
502
488
  html('tfoot')]).outerHTML], '']);
503
489
  assert.deepStrictEqual(
504
490
  inspect(parser([
505
491
  '~~~table',
506
- `-\n${[...Array(31)].map((_, i) => `: ${i + 1}`).join('\n')}\n# 32`,
507
- `-\n${[...Array(31)].map((_, i) => `: ${i + 1}`).join('\n')}\n#! 32`,
492
+ `-\n${[...Array(32)].map((_, i) => `: ${i + 1}`).join('\n')}\n# 33`,
493
+ `-\n${[...Array(32)].map((_, i) => `: ${i + 1}`).join('\n')}\n#! 33`,
508
494
  '~~~'
509
495
  ].join('\n'))),
510
496
  [[html('table', [
511
497
  html('thead'),
512
498
  html('tbody', [
513
499
  html('tr', [
514
- ...[...Array(31)].map((_, i) => html('td', `${i + 1}`)),
515
- html('th', '32'),
500
+ ...[...Array(32)].map((_, i) => html('td', `${i + 1}`)),
501
+ html('th', '33'),
516
502
  ]),
517
503
  html('tr', [
518
- ...[...Array(31)].map((_, i) => html('td', { class: 'highlight', highlight: 'h' }, `${i + 1}`)),
519
- html('th', { class: 'highlight' }, '32'),
504
+ ...[...Array(32)].map((_, i) => html('td', { class: 'highlight', highlight: 'h' }, `${i + 1}`)),
505
+ html('th', { class: 'highlight' }, '33'),
520
506
  ]),
521
507
  ]),
522
508
  html('tfoot')]).outerHTML], '']);
523
509
  assert.deepStrictEqual(
524
510
  inspect(parser([
525
511
  '~~~table',
526
- `-\n${[...Array(31)].map((_, i) => `# ${i + 1}`).join('\n')}\n#! 32`,
527
- `-\n${[...Array(32)].map((_, i) => `: ${i + 1}`).join('\n')}`,
512
+ `-\n${[...Array(32)].map((_, i) => `# ${i + 1}`).join('\n')}\n#! 33`,
513
+ `-\n${[...Array(33)].map((_, i) => `: ${i + 1}`).join('\n')}`,
528
514
  '~~~'
529
515
  ].join('\n'))),
530
516
  [[html('table', [
531
517
  html('thead', [
532
518
  html('tr', [
533
- ...[...Array(31)].map((_, i) => html('th', `${i + 1}`)),
534
- html('th', { class: 'highlight' }, '32'),
519
+ ...[...Array(32)].map((_, i) => html('th', `${i + 1}`)),
520
+ html('th', { class: 'highlight' }, '33'),
535
521
  ]),
536
522
  ]),
537
523
  html('tbody', [
538
524
  html('tr', [
539
- ...[...Array(31)].map((_, i) => html('td', `${i + 1}`)),
540
- html('td', { class: 'highlight', highlight: 'v' }, '32'),
525
+ ...[...Array(32)].map((_, i) => html('td', `${i + 1}`)),
526
+ html('td', { class: 'highlight', highlight: 'v' }, '33'),
541
527
  ]),
542
528
  ]),
543
529
  html('tfoot')]).outerHTML], '']);
@@ -668,6 +654,15 @@ describe('Unit: parser/block/extension/table', () => {
668
654
  ]).outerHTML], '']);
669
655
  });
670
656
 
657
+ it('type', () => {
658
+ assert.deepStrictEqual(
659
+ inspect(parser('~~~table/invalid\n~~~')),
660
+ [['<pre class="invalid" translate="no">~~~table/invalid\n~~~</pre>'], '']);
661
+ assert.deepStrictEqual(
662
+ inspect(parser('~~~table/grid\n~~~')),
663
+ [[html('table', { 'data-type': 'grid' }).outerHTML], '']);
664
+ });
665
+
671
666
  });
672
667
 
673
668
  });
@@ -1,13 +1,13 @@
1
- import { undefined, Array } from 'spica/global';
1
+ import { undefined, BigInt, Array } from 'spica/global';
2
2
  import { max, min, isArray } from 'spica/alias';
3
3
  import { ExtensionParser } from '../../block';
4
4
  import { Tree, eval } from '../../../combinator/data/parser';
5
- import { union, subsequence, inits, some, block, line, validate, fence, rewrite, creator, open, clear, convert, trim, dup, recover, lazy, fmap } from '../../../combinator';
5
+ import { union, subsequence, inits, some, block, line, validate, fence, rewrite, creator, open, clear, convert, trim, dup, lazy, fmap } from '../../../combinator';
6
6
  import { inline } from '../../inline';
7
7
  import { str, anyline, emptyline, contentline } from '../../source';
8
8
  import { localize } from '../../locale';
9
9
  import { visualize } from '../../util';
10
- import { html, defrag } from 'typed-dom/dom';
10
+ import { html, define, defrag } from 'typed-dom/dom';
11
11
  import { unshift, splice } from 'spica/array';
12
12
 
13
13
  import TableParser = ExtensionParser.TableParser;
@@ -15,7 +15,7 @@ import RowParser = TableParser.RowParser;
15
15
  import AlignParser = TableParser.AlignParser;
16
16
  import CellParser = TableParser.CellParser;
17
17
 
18
- const opener = /^(~{3,})table(?!\S)([^\n]*)(?:$|\n)/;
18
+ const opener = /^(~{3,})table(?:\/(\S+))?(?!\S)([^\n]*)(?:$|\n)/;
19
19
 
20
20
  export const segment: TableParser.SegmentParser = block(validate('~~~',
21
21
  clear(fence(opener, 10000))));
@@ -23,10 +23,10 @@ export const segment: TableParser.SegmentParser = block(validate('~~~',
23
23
  export const segment_: TableParser.SegmentParser = block(validate('~~~',
24
24
  clear(fence(opener, 10000, false))), false);
25
25
 
26
- export const table: TableParser = block(validate('~~~', recover(fmap(
26
+ export const table: TableParser = block(validate('~~~', fmap(
27
27
  fence(opener, 10000),
28
28
  // Bug: Type mismatch between outer and inner.
29
- ([body, overflow, closer, opener, delim, param]: string[], _, context) => {
29
+ ([body, overflow, closer, opener, delim, type, param]: string[], _, context) => {
30
30
  if (!closer || overflow || param.trimStart()) return [html('pre', {
31
31
  class: 'invalid',
32
32
  translate: 'no',
@@ -37,20 +37,21 @@ export const table: TableParser = block(validate('~~~', recover(fmap(
37
37
  overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
38
38
  'Invalid argument',
39
39
  }, `${opener}${body}${overflow || closer}`)];
40
- return eval(parser(body, context)) ?? [html('table')];
41
- }),
42
- (source, _, reason) =>
43
- reason instanceof Error && reason.message === 'Number of columns must be 32 or less'
44
- ? [[
45
- html('pre', {
46
- class: 'invalid',
47
- translate: 'no',
48
- 'data-invalid-syntax': 'table',
49
- 'data-invalid-type': 'content',
50
- 'data-invalid-message': reason.message,
51
- }, source),
52
- ], '']
53
- : (() => { throw reason; })())));
40
+ switch (type) {
41
+ case 'grid':
42
+ case undefined:
43
+ return (eval(parser(body, context)) ?? [html('table')])
44
+ .map(el => define(el, { 'data-type': type }));
45
+ default:
46
+ return [html('pre', {
47
+ class: 'invalid',
48
+ translate: 'no',
49
+ 'data-invalid-syntax': 'table',
50
+ 'data-invalid-type': 'argument',
51
+ 'data-invalid-message': 'Invalid table type',
52
+ }, `${opener}${body}${closer}`)];
53
+ }
54
+ })));
54
55
 
55
56
  const parser: TableParser = lazy(() => block(localize(fmap(
56
57
  some(union([row])),
@@ -144,7 +145,7 @@ function format(rows: Tree<RowParser>[]): HTMLTableSectionElement[] {
144
145
  const valigns: ('middle' | 'top' | 'bottom' | '')[] = [];
145
146
  let target = thead;
146
147
  let ranges: Record<number, Record<number, HTMLTableCellElement>> = {};
147
- let verticalHighlights = 0;
148
+ let verticalHighlights = 0n;
148
149
  ROW:
149
150
  for (let i = 0; i < rows.length; ++i) {
150
151
  // Copy to make them retryable.
@@ -214,25 +215,25 @@ function format(rows: Tree<RowParser>[]): HTMLTableSectionElement[] {
214
215
  }
215
216
  assert(valigns.length > 0);
216
217
  const row = html('tr');
217
- let heads = 0;
218
- let highlights = 0;
218
+ let heads = 0n;
219
+ let highlights = 0n;
219
220
  let hasDataCell = false;
220
- let lHeadCellIdx: number;
221
- let rHeadCellIdx: number;
222
- for (let j = 0; j < cells.length && cells.length <= 32; ++j) {
221
+ let lHeadCellIdx: bigint;
222
+ let rHeadCellIdx: bigint;
223
+ for (let j = 0, jn = 0n; j < cells.length; jn = BigInt(++j)) {
223
224
  const isVirtual = !!ranges[i]?.[j];
224
225
  const cell = isVirtual
225
226
  ? splice(cells, j, 0, undefined) && ranges[i][j]
226
227
  : cells[j];
227
228
  const isHeadCell = cell.tagName === 'TH';
228
- heads |= +isHeadCell << j;
229
- highlights |= +!!cell.classList.contains('highlight') << j;
229
+ heads |= BigInt(isHeadCell) << jn;
230
+ highlights |= BigInt(cell.classList.contains('highlight')) << jn;
230
231
  hasDataCell ||= !isHeadCell;
231
232
  if (isHeadCell && !hasDataCell) {
232
- lHeadCellIdx = j;
233
+ lHeadCellIdx = jn;
233
234
  }
234
235
  if (isHeadCell && hasDataCell) {
235
- rHeadCellIdx ??= j;
236
+ rHeadCellIdx ??= jn;
236
237
  }
237
238
  const rowSpan = cell.rowSpan;
238
239
  assert(rowSpan > 0);
@@ -247,8 +248,8 @@ function format(rows: Tree<RowParser>[]): HTMLTableSectionElement[] {
247
248
  assert(colSpan > 0);
248
249
  if (colSpan > 1) {
249
250
  splice(cells, j + 1, 0, ...Array(colSpan - 1));
250
- heads |= +`0b${`${heads & 1 << j && 1}`.repeat(colSpan)}` << j;
251
- highlights |= +`0b${`${highlights & 1 << j && 1}`.repeat(colSpan)}` << j;
251
+ heads |= BigInt(+`0b${`${heads & 1n << jn && 1}`.repeat(colSpan)}`) << jn;
252
+ highlights |= BigInt(+`0b${`${highlights & 1n << jn && 1}`.repeat(colSpan)}`) << jn;
252
253
  j += colSpan - 1;
253
254
  }
254
255
  if (target === thead) {
@@ -282,20 +283,19 @@ function format(rows: Tree<RowParser>[]): HTMLTableSectionElement[] {
282
283
  aligns[j] && cell.setAttribute('align', aligns[j]);
283
284
  valigns[j] && cell.setAttribute('valign', valigns[j]);
284
285
  }
285
- if (cells.length > 32) throw new Error('Number of columns must be 32 or less');
286
286
  target.appendChild(row);
287
287
  switch (target) {
288
288
  case thead:
289
289
  verticalHighlights = heads & highlights;
290
290
  continue;
291
291
  case tbody:
292
- lHeadCellIdx ??= -1;
293
- rHeadCellIdx ??= -1;
292
+ lHeadCellIdx ??= -1n;
293
+ rHeadCellIdx ??= -1n;
294
294
  const tHighlights = verticalHighlights;
295
295
  const horizontalHighlights = heads & highlights;
296
- const lHighlight = ~lHeadCellIdx && horizontalHighlights & 1 << lHeadCellIdx;
297
- const rHighlight = ~rHeadCellIdx && horizontalHighlights & 1 << rHeadCellIdx;
298
- for (let i = 0, m = 1; i < cells.length; ++i, m <<= 1) {
296
+ const lHighlight = ~lHeadCellIdx && horizontalHighlights & 1n << lHeadCellIdx;
297
+ const rHighlight = ~rHeadCellIdx && horizontalHighlights & 1n << rHeadCellIdx;
298
+ for (let i = 0, m = 1n; i < cells.length; ++i, m <<= 1n) {
299
299
  const cell = cells[i];
300
300
  if (!cell) continue;
301
301
  if (heads & m) continue;
@@ -52,7 +52,7 @@ describe('Unit: parser/block/reply/quote', () => {
52
52
  assert.deepStrictEqual(inspect(parser('> > a\n> > b\n> > c')), [['<span class="quote">&gt; &gt; a<br>&gt; &gt; b<br>&gt; &gt; c</span>', '<br>'], '']);
53
53
  assert.deepStrictEqual(inspect(parser('> > > a\n> > > b')), [['<span class="quote">&gt; &gt; &gt; a<br>&gt; &gt; &gt; b</span>', '<br>'], '']);
54
54
  assert.deepStrictEqual(inspect(parser('> #a')), [['<span class="quote">&gt; <a href="/hashtags/a" class="hashtag">#a</a></span>', '<br>'], '']);
55
- assert.deepStrictEqual(inspect(parser('> $a-b$')), [['<span class="quote">&gt; $a-b$</span>', '<br>'], '']);
55
+ assert.deepStrictEqual(inspect(parser('> $-a, $-b')), [['<span class="quote">&gt; $-a, $-b</span>', '<br>'], '']);
56
56
  assert.deepStrictEqual(inspect(parser('> $a=b$')), [['<span class="quote">&gt; <span class="math" translate="no" data-src="$a=b$">$a=b$</span></span>', '<br>'], '']);
57
57
  assert.deepStrictEqual(inspect(parser('> ${a}$')), [['<span class="quote">&gt; <span class="math" translate="no" data-src="${a}$">${a}$</span></span>', '<br>'], '']);
58
58
  });