securemark 0.266.0 → 0.267.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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.267.0
4
+
5
+ - Change horizontalrule syntax to pagebreak syntax.
6
+
3
7
  ## 0.266.0
4
8
 
5
9
  - Change index schemas.
package/README.md CHANGED
@@ -50,7 +50,7 @@ https://falsandtru.github.io/securemark/
50
50
  - Table (| |)
51
51
  - Blockquote (>, !>)
52
52
  - Preformattedtext (```)
53
- - HorizontalRule (---)
53
+ - Pagebreak (===)
54
54
  - Inline markups (_, *, `, []{}, {}, ![]{}, !{}, \[](), ++, ~~, (()), ...)
55
55
  - Inline HTML tags (\<bdi>, \<bdo>)
56
56
  - Autolink (https://host, user@host, @user)
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! securemark v0.266.0 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
1
+ /*! securemark v0.267.0 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"));
@@ -3908,7 +3908,7 @@ function bind(target, settings) {
3908
3908
  })
3909
3909
  };
3910
3910
 
3911
- if (context.id?.includes(':')) throw new Error('ID must not contain ":"');
3911
+ if (context.id?.match(/[^0-9a-z-]/i)) throw new Error('Invalid ID: ID must be alphanumeric');
3912
3912
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
3913
3913
  const blocks = [];
3914
3914
  const adds = [];
@@ -4226,7 +4226,6 @@ const url_1 = __webpack_require__(2261);
4226
4226
  const dom_1 = __webpack_require__(3252);
4227
4227
  function parse(source, opts = {}, context) {
4228
4228
  if (!(0, segment_1.validate)(source, segment_1.MAX_SEGMENT_SIZE)) throw new Error(`Too large input over ${segment_1.MAX_SEGMENT_SIZE.toLocaleString('en')} bytes`);
4229
- if (context?.id?.includes(':')) throw new Error('ID must not contain ":"');
4230
4229
  const url = (0, header_2.headers)(source).find(field => field.toLowerCase().startsWith('url:'))?.slice(4).trim() ?? '';
4231
4230
  source = !context ? (0, normalize_1.normalize)(source) : source;
4232
4231
  context = {
@@ -4242,6 +4241,7 @@ function parse(source, opts = {}, context) {
4242
4241
  })
4243
4242
  };
4244
4243
 
4244
+ if (context.id?.match(/[^0-9a-z-]/i)) throw new Error('Invalid ID: ID must be alphanumeric');
4245
4245
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
4246
4246
  const node = (0, dom_1.frag)();
4247
4247
  let index = 0;
@@ -4314,7 +4314,7 @@ Object.defineProperty(exports, "__esModule", ({
4314
4314
  exports.block = void 0;
4315
4315
  const combinator_1 = __webpack_require__(2087);
4316
4316
  const source_1 = __webpack_require__(6743);
4317
- const horizontalrule_1 = __webpack_require__(9967);
4317
+ const pagebreak_1 = __webpack_require__(4107);
4318
4318
  const heading_1 = __webpack_require__(4623);
4319
4319
  const ulist_1 = __webpack_require__(5425);
4320
4320
  const olist_1 = __webpack_require__(7471);
@@ -4335,7 +4335,7 @@ exports.block = (0, combinator_1.creation)(1, false, error((0, combinator_1.rese
4335
4335
  clock: 50 * 1000,
4336
4336
  recursion: 20
4337
4337
  }
4338
- }, (0, combinator_1.union)([source_1.emptyline, horizontalrule_1.horizontalrule, heading_1.heading, ulist_1.ulist, olist_1.olist, ilist_1.ilist, dlist_1.dlist, table_1.table, codeblock_1.codeblock, mathblock_1.mathblock, extension_1.extension, sidefence_1.sidefence, blockquote_1.blockquote, reply_1.reply, paragraph_1.paragraph]))));
4338
+ }, (0, combinator_1.union)([source_1.emptyline, pagebreak_1.pagebreak, heading_1.heading, ulist_1.ulist, olist_1.olist, ilist_1.ilist, dlist_1.dlist, table_1.table, codeblock_1.codeblock, mathblock_1.mathblock, extension_1.extension, sidefence_1.sidefence, blockquote_1.blockquote, reply_1.reply, paragraph_1.paragraph]))));
4339
4339
  function error(parser) {
4340
4340
  return (0, combinator_1.recover)((0, combinator_1.fallback)((0, combinator_1.open)('\x07', ({
4341
4341
  source
@@ -5168,22 +5168,6 @@ exports.heading = (0, combinator_1.block)((0, combinator_1.rewrite)(exports.segm
5168
5168
 
5169
5169
  /***/ }),
5170
5170
 
5171
- /***/ 9967:
5172
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
5173
-
5174
- "use strict";
5175
-
5176
-
5177
- Object.defineProperty(exports, "__esModule", ({
5178
- value: true
5179
- }));
5180
- exports.horizontalrule = void 0;
5181
- const combinator_1 = __webpack_require__(2087);
5182
- const dom_1 = __webpack_require__(3252);
5183
- exports.horizontalrule = (0, combinator_1.block)((0, combinator_1.line)((0, combinator_1.focus)(/^-{3,}[^\S\n]*(?:$|\n)/, () => [[(0, dom_1.html)('hr')], ''])));
5184
-
5185
- /***/ }),
5186
-
5187
5171
  /***/ 238:
5188
5172
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
5189
5173
 
@@ -5356,6 +5340,22 @@ function format(el, type, form) {
5356
5340
 
5357
5341
  /***/ }),
5358
5342
 
5343
+ /***/ 4107:
5344
+ /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
5345
+
5346
+ "use strict";
5347
+
5348
+
5349
+ Object.defineProperty(exports, "__esModule", ({
5350
+ value: true
5351
+ }));
5352
+ exports.pagebreak = void 0;
5353
+ const combinator_1 = __webpack_require__(2087);
5354
+ const dom_1 = __webpack_require__(3252);
5355
+ exports.pagebreak = (0, combinator_1.block)((0, combinator_1.line)((0, combinator_1.focus)(/^={3,}[^\S\n]*(?:$|\n)/, () => [[(0, dom_1.html)('hr')], ''])));
5356
+
5357
+ /***/ }),
5358
+
5359
5359
  /***/ 6457:
5360
5360
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
5361
5361
 
@@ -5805,7 +5805,7 @@ const link_1 = __webpack_require__(9628);
5805
5805
  const source_1 = __webpack_require__(6743);
5806
5806
  const dom_1 = __webpack_require__(3252);
5807
5807
  // https://example/@user must be a user page or a redirect page going there.
5808
- exports.account = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.constraint)(1 /* State.shortcut */, false, (0, combinator_1.open)('@', (0, combinator_1.tails)([(0, combinator_1.verify)((0, source_1.str)(/^[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?)*\//i), ([source]) => source.length <= 253 + 1), (0, source_1.str)(/^[a-z](?:-(?=[0-9a-z])|[0-9a-z]){0,63}/i)]))), (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `https://${source.slice(1).replace('/', '/@')}` : `/${source}`} }`, (0, combinator_1.union)([link_1.unsafelink]))), ([el]) => [(0, dom_1.define)(el, {
5808
+ exports.account = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.constraint)(1 /* State.shortcut */, false, (0, combinator_1.open)('@', (0, combinator_1.tails)([(0, source_1.str)(/^[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?)*\//i), (0, source_1.str)(/^[a-z][0-9a-z]*(?:-[0-9a-z]+)*/i)]))), (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `https://${source.slice(1).replace('/', '/@')}` : `/${source}`} }`, (0, combinator_1.union)([link_1.unsafelink]))), ([el]) => [(0, dom_1.define)(el, {
5809
5809
  class: 'account'
5810
5810
  })]));
5811
5811
 
@@ -5826,11 +5826,12 @@ const link_1 = __webpack_require__(9628);
5826
5826
  const dom_1 = __webpack_require__(3252);
5827
5827
  // Timeline(pseudonym): user/tid
5828
5828
  // Thread(anonymous): cid
5829
- // tid: YYYY-MM-DD-HH-MM-SS-TMZ
5830
- // cid: YYYY-MM-DD-HH-MM-SS-mmm-TMZ
5829
+ // UTC
5830
+ // tid: YYYY-MMDD-HHMM-SS
5831
+ // cid: YYYY-MMDD-HHMM-SSmmm
5831
5832
  // 内部表現はUnixTimeに統一する(時系列順)
5832
5833
  // 外部表現は投稿ごとに投稿者の投稿時のタイムゾーンに統一する(非時系列順)
5833
- exports.anchor = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('>>', (0, combinator_1.fmap)((0, combinator_1.constraint)(1 /* State.shortcut */, false, (0, combinator_1.focus)(/^>>(?:[a-z][0-9a-z]*(?:-[0-9a-z]+)*\/)?[0-9a-z]+(?:-[0-9a-z]+)*(?![0-9a-z@#:])/i, (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `/@${source.slice(2).replace('/', '/timeline/')}` : `?at=${source.slice(2)}`} }`, (0, combinator_1.union)([link_1.unsafelink])))), ([el]) => [(0, dom_1.define)(el, {
5834
+ exports.anchor = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('>>', (0, combinator_1.fmap)((0, combinator_1.constraint)(1 /* State.shortcut */, false, (0, combinator_1.focus)(/^>>(?:[a-z][0-9a-z]*(?:-[0-9a-z]+)*\/)?[0-9a-z]+(?:-[0-9a-z]+)*(?![0-9a-z@#:])/i, (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `/@${source.slice(2).replace('/', '/timeline?at=')}` : `?at=${source.slice(2)}`} }`, (0, combinator_1.union)([link_1.unsafelink])))), ([el]) => [(0, dom_1.define)(el, {
5834
5835
  class: 'anchor'
5835
5836
  })])));
5836
5837
 
@@ -5854,7 +5855,6 @@ const dom_1 = __webpack_require__(3252);
5854
5855
  // https://example/@user?ch=a+b must be a user channel page or a redirect page going there.
5855
5856
  exports.channel = (0, combinator_1.validate)('@', (0, combinator_1.bind)((0, combinator_1.sequence)([account_1.account, (0, combinator_1.some)(hashtag_1.hashtag)]), (es, rest) => {
5856
5857
  const source = (0, util_1.stringify)(es);
5857
- if (source.includes('/', source.indexOf('#'))) return;
5858
5858
  const el = es[0];
5859
5859
  const url = `${el.getAttribute('href')}?ch=${source.slice(source.indexOf('#') + 1).replace(/#/g, '+')}`;
5860
5860
  return [[(0, dom_1.define)(el, {
@@ -5927,9 +5927,9 @@ const dom_1 = __webpack_require__(3252);
5927
5927
  // https://example/hashtags/a must be a hashtag page or a redirect page going there.
5928
5928
  // https://github.com/tc39/proposal-regexp-unicode-property-escapes#matching-emoji
5929
5929
  exports.emoji = String.raw`\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F`;
5930
- exports.hashtag = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.constraint)(1 /* State.shortcut */, false, (0, combinator_1.open)('#', (0, combinator_1.tails)([(0, combinator_1.verify)((0, source_1.str)(/^[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?)*\//i), ([source]) => source.length <= 253 + 1), (0, combinator_1.verify)((0, source_1.str)(new RegExp([/^(?=(?:[0-9]{1,127}_?)?(?:[^\d\p{C}\p{S}\p{P}\s]|emoji|'))/u.source, /(?:[^\p{C}\p{S}\p{P}\s]|emoji|(?<!')'|_(?=[^\p{C}\p{S}\p{P}\s]|emoji|')){1,128}/u.source, /(?!_?(?:[^\p{C}\p{S}\p{P}\s]|emoji|(?<!')'))/u.source].join('').replace(/emoji/g, exports.emoji), 'u')), ([source]) => source.length <= 128)]))), (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `https://${source.slice(1).replace('/', '/hashtags/')}` : `/hashtags/${source.slice(1)}`} }`, (0, combinator_1.union)([link_1.unsafelink]))), ([el]) => [(0, dom_1.define)(el, {
5930
+ exports.hashtag = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.constraint)(1 /* State.shortcut */, false, (0, combinator_1.open)('#', (0, source_1.str)(new RegExp([/^(?=(?:[0-9]{1,15})?(?:[^\d\p{C}\p{S}\p{P}\s]|emoji|'))/u.source, /(?:[^\p{C}\p{S}\p{P}\s]|emoji|(?<!')'|_(?=[^\p{C}\p{S}\p{P}\s]|emoji|'))+/u.source].join('').replace(/emoji/g, exports.emoji), 'u')))), (0, combinator_1.convert)(source => `[${source}]{ ${`/hashtags/${source.slice(1)}`} }`, (0, combinator_1.union)([link_1.unsafelink]))), ([el]) => [(0, dom_1.define)(el, {
5931
5931
  class: 'hashtag'
5932
- }, el.innerText)]));
5932
+ })]));
5933
5933
 
5934
5934
  /***/ }),
5935
5935
 
@@ -6489,6 +6489,7 @@ function resolve(uri, host, source) {
6489
6489
  case uri.slice(0, 2) === '^/':
6490
6490
  const last = host.pathname.slice(host.pathname.lastIndexOf('/') + 1);
6491
6491
  return last.includes('.') // isFile
6492
+ // Exclude ISO 6709.
6492
6493
  && /^[0-9]*[a-z][0-9a-z]*$/i.test(last.slice(last.lastIndexOf('.') + 1)) ? `${host.pathname.slice(0, -last.length)}${uri.slice(2)}` : `${host.pathname.replace(/\/?$/, '/')}${uri.slice(2)}`;
6493
6494
  case host.origin === source.origin && host.pathname === source.pathname:
6494
6495
  case uri.slice(0, 2) === '//':
package/index.d.ts CHANGED
@@ -28,8 +28,7 @@ export type ParserOptions = Omit<Partial<ParserSettings>, 'chunk'>;
28
28
  export interface ParserSettings {
29
29
  // Host URL.
30
30
  readonly host?: URL;
31
- // ID of comments and timelines.
32
- // Must not contain ":".
31
+ // Alphanumeric ID of comments and timelines.
33
32
  readonly id?: string;
34
33
  // For editing.
35
34
  readonly caches?: Partial<Caches>;
@@ -40,12 +39,12 @@ export interface ParserSettings {
40
39
  }
41
40
 
42
41
  export type Progress =
43
- | { type: 'segment', value: string; }
44
- | { type: 'block', value: HTMLElement }
45
- | { type: 'figure', value: HTMLAnchorElement }
46
- | { type: 'footnote', value: HTMLLIElement | HTMLElement }
47
- | { type: 'break' }
48
- | { type: 'cancel' };
42
+ | { readonly type: 'segment'; readonly value: string; }
43
+ | { readonly type: 'block'; readonly value: HTMLElement; }
44
+ | { readonly type: 'figure'; readonly value: HTMLAnchorElement; }
45
+ | { readonly type: 'footnote'; readonly value: HTMLLIElement | HTMLElement; }
46
+ | { readonly type: 'break'; }
47
+ | { readonly type: 'cancel'; };
49
48
 
50
49
  export interface RenderingOptions {
51
50
  readonly code?: (target: HTMLElement, cache?: Dict<string, HTMLElement>) => void;
package/markdown.d.ts CHANGED
@@ -59,7 +59,7 @@ export namespace MarkdownParser {
59
59
  Markdown<'block'>,
60
60
  Parser<HTMLElement, Context, [
61
61
  SourceParser.EmptyLineParser,
62
- BlockParser.HorizontalRuleParser,
62
+ BlockParser.PagebreakParser,
63
63
  BlockParser.HeadingParser,
64
64
  BlockParser.UListParser,
65
65
  BlockParser.OListParser,
@@ -77,9 +77,9 @@ export namespace MarkdownParser {
77
77
  }
78
78
  export namespace BlockParser {
79
79
  interface Block<T extends string> extends Markdown<`block/${T}`> { }
80
- export interface HorizontalRuleParser extends
81
- // ---
82
- Block<'horizontalrule'>,
80
+ export interface PagebreakParser extends
81
+ // ===
82
+ Block<'pagebreak'>,
83
83
  Parser<HTMLHRElement, Context, [
84
84
  SourceParser.StrParser,
85
85
  ]> {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.266.0",
3
+ "version": "0.267.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",
@@ -26,7 +26,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
26
26
  host: settings.host ?? new ReadonlyURL(location.pathname, location.origin),
27
27
  memo: new Memo({ targets: State.backtrackers }),
28
28
  };
29
- if (context.id?.includes(':')) throw new Error('ID must not contain ":"');
29
+ if (context.id?.match(/[^0-9a-z-]/i)) throw new Error('Invalid ID: ID must be alphanumeric');
30
30
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
31
31
  assert(!settings.id);
32
32
  type Block = readonly [segment: string, blocks: readonly HTMLElement[], url: string];
@@ -99,7 +99,6 @@ describe('Unit: parser/api/parse', () => {
99
99
  '@a#b',
100
100
  '@domain/a#b',
101
101
  '#a',
102
- '#domain/a',
103
102
  '[#a]',
104
103
  '$-a\n$$\n$$',
105
104
  '$-a',
@@ -122,7 +121,6 @@ describe('Unit: parser/api/parse', () => {
122
121
  '<p><a class="channel" href="https://source/@a?ch=b" target="_blank">@a#b</a></p>',
123
122
  '<p><a class="channel" href="https://domain/@a?ch=b" target="_blank">@domain/a#b</a></p>',
124
123
  '<p><a class="hashtag" href="https://source/hashtags/a" target="_blank">#a</a></p>',
125
- '<p><a class="hashtag" href="https://domain/hashtags/a" target="_blank">#domain/a</a></p>',
126
124
  '<p><a class="index" href="#index::a">a</a></p>',
127
125
  '<figure data-type="math" data-label="$-a" data-group="$" data-number="1" id="label:$-a"><figcaption><span class="figindex">(1)</span><span class="figtext"></span></figcaption><div><div class="math" translate="no">$$\n$$</div></div></figure>',
128
126
  '<p><a class="label" data-label="$-a" href="#label:$-a">(1)</a></p>',
@@ -19,7 +19,6 @@ interface Options extends ParserOptions {
19
19
 
20
20
  export function parse(source: string, opts: Options = {}, context?: MarkdownParser.Context): DocumentFragment {
21
21
  if (!validate(source, MAX_SEGMENT_SIZE)) throw new Error(`Too large input over ${MAX_SEGMENT_SIZE.toLocaleString('en')} bytes`);
22
- if (context?.id?.includes(':')) throw new Error('ID must not contain ":"');
23
22
  const url = headers(source).find(field => field.toLowerCase().startsWith('url:'))?.slice(4).trim() ?? '';
24
23
  source = !context ? normalize(source) : source;
25
24
  assert(!context?.delimiters);
@@ -33,6 +32,7 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
33
32
  },
34
33
  memo: new Memo({ targets: State.backtrackers }),
35
34
  };
35
+ if (context.id?.match(/[^0-9a-z-]/i)) throw new Error('Invalid ID: ID must be alphanumeric');
36
36
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
37
37
  const node = frag();
38
38
  let index = 0;
@@ -0,0 +1,31 @@
1
+ import { pagebreak } from './pagebreak';
2
+ import { some } from '../../combinator';
3
+ import { inspect } from '../../debug.test';
4
+
5
+ describe('Unit: parser/block/pagebreak', () => {
6
+ describe('pagebreak', () => {
7
+ const parser = (source: string) => some(pagebreak)({ source, context: {} });
8
+
9
+ it('invalid', () => {
10
+ assert.deepStrictEqual(inspect(parser('')), undefined);
11
+ assert.deepStrictEqual(inspect(parser('\n')), undefined);
12
+ assert.deepStrictEqual(inspect(parser('=')), undefined);
13
+ assert.deepStrictEqual(inspect(parser('==')), undefined);
14
+ assert.deepStrictEqual(inspect(parser('==\n=')), undefined);
15
+ assert.deepStrictEqual(inspect(parser('===a')), undefined);
16
+ assert.deepStrictEqual(inspect(parser('===\na')), undefined);
17
+ assert.deepStrictEqual(inspect(parser('= = =')), undefined);
18
+ assert.deepStrictEqual(inspect(parser(' ===')), undefined);
19
+ assert.deepStrictEqual(inspect(parser('---')), undefined);
20
+ });
21
+
22
+ it('valid', () => {
23
+ assert.deepStrictEqual(inspect(parser('===')), [['<hr>'], '']);
24
+ assert.deepStrictEqual(inspect(parser('=== ')), [['<hr>'], '']);
25
+ assert.deepStrictEqual(inspect(parser('===\n')), [['<hr>'], '']);
26
+ assert.deepStrictEqual(inspect(parser('====')), [['<hr>'], '']);
27
+ });
28
+
29
+ });
30
+
31
+ });
@@ -0,0 +1,7 @@
1
+ import { PagebreakParser } from '../block';
2
+ import { block, line, focus } from '../../combinator';
3
+ import { html } from 'typed-dom/dom';
4
+
5
+ export const pagebreak: PagebreakParser = block(line(focus(
6
+ /^={3,}[^\S\n]*(?:$|\n)/,
7
+ () => [[html('hr')], ''])));
@@ -1,7 +1,7 @@
1
1
  import { MarkdownParser } from '../../markdown';
2
2
  import { union, reset, creation, open, fallback, recover } from '../combinator';
3
3
  import { emptyline } from './source';
4
- import { horizontalrule } from './block/horizontalrule';
4
+ import { pagebreak } from './block/pagebreak';
5
5
  import { heading } from './block/heading';
6
6
  import { ulist } from './block/ulist';
7
7
  import { olist } from './block/olist';
@@ -19,7 +19,7 @@ import { rnd0Z } from 'spica/random';
19
19
  import { html } from 'typed-dom/dom';
20
20
 
21
21
  export import BlockParser = MarkdownParser.BlockParser;
22
- export import HorizontalRuleParser = BlockParser.HorizontalRuleParser;
22
+ export import PagebreakParser = BlockParser.PagebreakParser;
23
23
  export import HeadingParser = BlockParser.HeadingParser;
24
24
  export import UListParser = BlockParser.UListParser;
25
25
  export import OListParser = BlockParser.OListParser;
@@ -38,7 +38,7 @@ export const block: BlockParser = creation(1, false, error(
38
38
  reset({ resources: { clock: 50 * 1000, recursion: 20 } },
39
39
  union([
40
40
  emptyline,
41
- horizontalrule,
41
+ pagebreak,
42
42
  heading,
43
43
  ulist,
44
44
  olist,
@@ -1,5 +1,5 @@
1
1
  import { AutolinkParser } from '../../inline';
2
- import { union, constraint, tails, verify, rewrite, open, convert, fmap, lazy } from '../../../combinator';
2
+ import { union, constraint, tails, rewrite, open, convert, fmap, lazy } from '../../../combinator';
3
3
  import { unsafelink } from '../link';
4
4
  import { str } from '../../source';
5
5
  import { State } from '../../context';
@@ -12,10 +12,8 @@ export const account: AutolinkParser.AccountParser = lazy(() => fmap(rewrite(
12
12
  open(
13
13
  '@',
14
14
  tails([
15
- verify(
16
- str(/^[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?)*\//i),
17
- ([source]) => source.length <= 253 + 1),
18
- str(/^[a-z](?:-(?=[0-9a-z])|[0-9a-z]){0,63}/i),
15
+ str(/^[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?)*\//i),
16
+ str(/^[a-z][0-9a-z]*(?:-[0-9a-z]+)*/i),
19
17
  ]))),
20
18
  convert(
21
19
  source =>
@@ -28,8 +28,8 @@ describe('Unit: parser/inline/autolink/anchor', () => {
28
28
  assert.deepStrictEqual(inspect(parser('>>0-a')), [['<a class="anchor" href="?at=0-a">&gt;&gt;0-a</a>'], '']);
29
29
  assert.deepStrictEqual(inspect(parser('>>0-A')), [['<a class="anchor" href="?at=0-A">&gt;&gt;0-A</a>'], '']);
30
30
  assert.deepStrictEqual(inspect(parser('>>0--a')), [['<a class="anchor" href="?at=0">&gt;&gt;0</a>'], '--a']);
31
- assert.deepStrictEqual(inspect(parser('>>2000-01-31-23-59-59-999-JST')), [['<a class="anchor" href="?at=2000-01-31-23-59-59-999-JST">&gt;&gt;2000-01-31-23-59-59-999-JST</a>'], '']);
32
- assert.deepStrictEqual(inspect(parser('>>A/2000-01-31-23-59-59-JST')), [['<a class="anchor" href="/@A/timeline/2000-01-31-23-59-59-JST">&gt;&gt;A/2000-01-31-23-59-59-JST</a>'], '']);
31
+ assert.deepStrictEqual(inspect(parser('>>2000-0131-2359-59999')), [['<a class="anchor" href="?at=2000-0131-2359-59999">&gt;&gt;2000-0131-2359-59999</a>'], '']);
32
+ assert.deepStrictEqual(inspect(parser('>>A/2000-0131-2359-59')), [['<a class="anchor" href="/@A/timeline?at=2000-0131-2359-59">&gt;&gt;A/2000-0131-2359-59</a>'], '']);
33
33
  });
34
34
 
35
35
  });
@@ -7,8 +7,9 @@ import { define } from 'typed-dom/dom';
7
7
  // Timeline(pseudonym): user/tid
8
8
  // Thread(anonymous): cid
9
9
 
10
- // tid: YYYY-MM-DD-HH-MM-SS-TMZ
11
- // cid: YYYY-MM-DD-HH-MM-SS-mmm-TMZ
10
+ // UTC
11
+ // tid: YYYY-MMDD-HHMM-SS
12
+ // cid: YYYY-MMDD-HHMM-SSmmm
12
13
 
13
14
  // 内部表現はUnixTimeに統一する(時系列順)
14
15
  // 外部表現は投稿ごとに投稿者の投稿時のタイムゾーンに統一する(非時系列順)
@@ -21,7 +22,7 @@ export const anchor: AutolinkParser.AnchorParser = lazy(() => validate('>>', fma
21
22
  source =>
22
23
  `[${source}]{ ${
23
24
  source.includes('/')
24
- ? `/@${source.slice(2).replace('/', '/timeline/')}`
25
+ ? `/@${source.slice(2).replace('/', '/timeline?at=')}`
25
26
  : `?at=${source.slice(2)}`
26
27
  } }`,
27
28
  union([unsafelink])))),
@@ -16,8 +16,6 @@ describe('Unit: parser/inline/autolink/channel', () => {
16
16
  assert.deepStrictEqual(inspect(parser('@a#1@b')), [['@a#1@b'], '']);
17
17
  assert.deepStrictEqual(inspect(parser('@a#b#')), [['@a#b#'], '']);
18
18
  assert.deepStrictEqual(inspect(parser('@a#b#1')), [['@a#b#1'], '']);
19
- assert.deepStrictEqual(inspect(parser('@a#domain/b')), [['@a#domain/b'], '']);
20
- assert.deepStrictEqual(inspect(parser('@domain/a#domain/b')), [['@domain/a#domain/b'], '']);
21
19
  assert.deepStrictEqual(inspect(parser(' @a#b')), undefined);
22
20
  });
23
21
 
@@ -14,7 +14,6 @@ export const channel: AutolinkParser.ChannelParser = validate('@', bind(
14
14
  ]),
15
15
  (es, rest) => {
16
16
  const source = stringify(es);
17
- if (source.includes('/', source.indexOf('#'))) return;
18
17
  const el = es[0];
19
18
  const url = `${el.getAttribute('href')}?ch=${source.slice(source.indexOf('#') + 1).replace(/#/g, '+')}`;
20
19
  return [[define(el, { class: 'channel', href: url }, source)], rest];
@@ -33,7 +33,7 @@ describe('Unit: parser/inline/autolink/hashnum', () => {
33
33
  assert.deepStrictEqual(inspect(parser('あ#1')), [['あ#1'], '']);
34
34
  assert.deepStrictEqual(inspect(parser(' #1')), undefined);
35
35
  assert.deepStrictEqual(inspect(parser('#12345678901234567')), [['#12345678901234567'], '']);
36
- assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(128)}a`)), [[`#${'1'.repeat(128)}a`], '']);
36
+ assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(16)}a`)), [[`#${'1'.repeat(16)}a`], '']);
37
37
  });
38
38
 
39
39
  it('valid', () => {
@@ -34,9 +34,6 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
34
34
  assert.deepStrictEqual(inspect(parser('a##1')), [['a##1'], '']);
35
35
  assert.deepStrictEqual(inspect(parser('a##b')), [['a##b'], '']);
36
36
  assert.deepStrictEqual(inspect(parser('あ#b')), [['あ#b'], '']);
37
- assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(127)}_`)), [[`#${'1'.repeat(127)}`], '_']);
38
- assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(127)}_a`)), [[`#${'1'.repeat(127)}`], '_a']);
39
- assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(127)}_'a`)), [[`#${'1'.repeat(127)}`], `_'a`]);
40
37
  assert.deepStrictEqual(inspect(parser(' #a')), undefined);
41
38
  });
42
39
 
@@ -51,15 +48,13 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
51
48
  assert.deepStrictEqual(inspect(parser('#a(b')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '(b']);
52
49
  assert.deepStrictEqual(inspect(parser('#a(b)')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '(b)']);
53
50
  assert.deepStrictEqual(inspect(parser('#a_')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '_']);
54
- assert.deepStrictEqual(inspect(parser('#a__b')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '__b']);
55
51
  assert.deepStrictEqual(inspect(parser('#a_b')), [['<a class="hashtag" href="/hashtags/a_b">#a_b</a>'], '']);
52
+ assert.deepStrictEqual(inspect(parser('#a__b')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '__b']);
56
53
  assert.deepStrictEqual(inspect(parser('#あ')), [['<a class="hashtag" href="/hashtags/あ">#あ</a>'], '']);
57
54
  assert.deepStrictEqual(inspect(parser('#👩')), [['<a class="hashtag" href="/hashtags/👩">#👩</a>'], '']);
58
55
  assert.deepStrictEqual(inspect(parser('#1a')), [['<a class="hashtag" href="/hashtags/1a">#1a</a>'], '']);
59
56
  assert.deepStrictEqual(inspect(parser('#1あ')), [['<a class="hashtag" href="/hashtags/1あ">#1あ</a>'], '']);
60
57
  assert.deepStrictEqual(inspect(parser('#1👩')), [['<a class="hashtag" href="/hashtags/1👩">#1👩</a>'], '']);
61
- assert.deepStrictEqual(inspect(parser('#domain/a')), [['<a class="hashtag" href="https://domain/hashtags/a" target="_blank">#domain/a</a>'], '']);
62
- assert.deepStrictEqual(inspect(parser('#domain.co.jp/a')), [['<a class="hashtag" href="https://domain.co.jp/hashtags/a" target="_blank">#domain.co.jp/a</a>'], '']);
63
58
  assert.deepStrictEqual(inspect(parser(`#'0`)), [[`<a class="hashtag" href="/hashtags/'0">#'0</a>`], '']);
64
59
  assert.deepStrictEqual(inspect(parser(`#'00`)), [[`<a class="hashtag" href="/hashtags/'00">#'00</a>`], '']);
65
60
  assert.deepStrictEqual(inspect(parser(`#1'`)), [[`<a class="hashtag" href="/hashtags/1'">#1'</a>`], '']);
@@ -69,6 +64,7 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
69
64
  assert.deepStrictEqual(inspect(parser(`#a'b`)), [[`<a class="hashtag" href="/hashtags/a'b">#a'b</a>`], '']);
70
65
  assert.deepStrictEqual(inspect(parser(`#a'_b`)), [[`<a class="hashtag" href="/hashtags/a'_b">#a'_b</a>`], '']);
71
66
  assert.deepStrictEqual(inspect(parser(`#a_'b`)), [[`<a class="hashtag" href="/hashtags/a_'b">#a_'b</a>`], '']);
67
+ assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(15)}a`)), [[`<a class="hashtag" href="/hashtags/${'1'.repeat(15)}a">#${'1'.repeat(15)}a</a>`], '']);
72
68
  });
73
69
 
74
70
  });
@@ -1,5 +1,5 @@
1
1
  import { AutolinkParser } from '../../inline';
2
- import { union, tails, constraint, verify, rewrite, open, convert, fmap, lazy } from '../../../combinator';
2
+ import { union, constraint, rewrite, open, convert, fmap, lazy } from '../../../combinator';
3
3
  import { unsafelink } from '../link';
4
4
  import { str } from '../../source';
5
5
  import { State } from '../../context';
@@ -14,24 +14,11 @@ export const hashtag: AutolinkParser.HashtagParser = lazy(() => fmap(rewrite(
14
14
  constraint(State.shortcut, false,
15
15
  open(
16
16
  '#',
17
- tails([
18
- verify(
19
- str(/^[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?)*\//i),
20
- ([source]) => source.length <= 253 + 1),
21
- verify(
22
- str(new RegExp([
23
- /^(?=(?:[0-9]{1,127}_?)?(?:[^\d\p{C}\p{S}\p{P}\s]|emoji|'))/u.source,
24
- /(?:[^\p{C}\p{S}\p{P}\s]|emoji|(?<!')'|_(?=[^\p{C}\p{S}\p{P}\s]|emoji|')){1,128}/u.source,
25
- /(?!_?(?:[^\p{C}\p{S}\p{P}\s]|emoji|(?<!')'))/u.source,
26
- ].join('').replace(/emoji/g, emoji), 'u')),
27
- ([source]) => source.length <= 128),
28
- ]))),
17
+ str(new RegExp([
18
+ /^(?=(?:[0-9]{1,15})?(?:[^\d\p{C}\p{S}\p{P}\s]|emoji|'))/u.source,
19
+ /(?:[^\p{C}\p{S}\p{P}\s]|emoji|(?<!')'|_(?=[^\p{C}\p{S}\p{P}\s]|emoji|'))+/u.source,
20
+ ].join('').replace(/emoji/g, emoji), 'u')))),
29
21
  convert(
30
- source =>
31
- `[${source}]{ ${
32
- source.includes('/')
33
- ? `https://${source.slice(1).replace('/', '/hashtags/')}`
34
- : `/hashtags/${source.slice(1)}`
35
- } }`,
22
+ source => `[${source}]{ ${`/hashtags/${source.slice(1)}`} }`,
36
23
  union([unsafelink]))),
37
- ([el]) => [define(el, { class: 'hashtag' }, el.innerText)]));
24
+ ([el]) => [define(el, { class: 'hashtag' })]));
@@ -9,6 +9,7 @@ export function indexee(parser: Parser<HTMLElement, MarkdownParser.Context>, opt
9
9
  }
10
10
 
11
11
  export function identity(id: string | undefined, text: string, name: 'index' | 'mark' = 'index'): string | undefined {
12
+ assert(!id?.match(/[^0-9a-z-]/i));
12
13
  assert(!text.includes('\n'));
13
14
  if (id === '') return undefined;
14
15
  text &&= text.trim().replace(/\s+/g, '_');
@@ -180,6 +180,7 @@ export function resolve(uri: string, host: URL | Location, source: URL | Locatio
180
180
  case uri.slice(0, 2) === '^/':
181
181
  const last = host.pathname.slice(host.pathname.lastIndexOf('/') + 1);
182
182
  return last.includes('.') // isFile
183
+ // Exclude ISO 6709.
183
184
  && /^[0-9]*[a-z][0-9a-z]*$/i.test(last.slice(last.lastIndexOf('.') + 1))
184
185
  ? `${host.pathname.slice(0, -last.length)}${uri.slice(2)}`
185
186
  : `${host.pathname.replace(/\/?$/, '/')}${uri.slice(2)}`;
@@ -122,6 +122,7 @@ describe('Unit: parser/inline', () => {
122
122
  assert.deepStrictEqual(inspect(parser('a@b')), [['<a class="email" href="mailto:a@b">a@b</a>'], '']);
123
123
  assert.deepStrictEqual(inspect(parser('_a@b')), [['_', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
124
124
  assert.deepStrictEqual(inspect(parser('_a@b_')), [['<em><a class="email" href="mailto:a@b">a@b</a></em>'], '']);
125
+ assert.deepStrictEqual(inspect(parser('_a_b@c_')), [['<em><a class="email" href="mailto:a_b@c">a_b@c</a></em>'], '']);
125
126
  assert.deepStrictEqual(inspect(parser('*a@b*')), [['<strong><a class="email" href="mailto:a@b">a@b</a></strong>'], '']);
126
127
  assert.deepStrictEqual(inspect(parser('(a@b)')), [['<span class="paren">(<a class="email" href="mailto:a@b">a@b</a>)</span>'], '']);
127
128
  assert.deepStrictEqual(inspect(parser(' a@b')), [[' ', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
@@ -130,9 +131,7 @@ describe('Unit: parser/inline', () => {
130
131
 
131
132
  it('channel', () => {
132
133
  assert.deepStrictEqual(inspect(parser('@a#b')), [['<a class="channel" href="/@a?ch=b">@a#b</a>'], '']);
133
- assert.deepStrictEqual(inspect(parser('@a#domain/b')), [['@a#domain/b'], '']);
134
134
  assert.deepStrictEqual(inspect(parser('@domain/a#b')), [['<a class="channel" href="https://domain/@a?ch=b" target="_blank">@domain/a#b</a>'], '']);
135
- assert.deepStrictEqual(inspect(parser('@domain/a#domain/b')), [['@domain/a#domain/b'], '']);
136
135
  assert.deepStrictEqual(inspect(parser('_@a#b')), [['_', '<a class="channel" href="/@a?ch=b">@a#b</a>'], '']);
137
136
  assert.deepStrictEqual(inspect(parser(' @a#b')), [[' ', '<a class="channel" href="/@a?ch=b">@a#b</a>'], '']);
138
137
  });
@@ -152,6 +151,8 @@ describe('Unit: parser/inline', () => {
152
151
  assert.deepStrictEqual(inspect(parser('#a')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '']);
153
152
  assert.deepStrictEqual(inspect(parser('#a\nb\n#c\n[#d]')), [['<a class="hashtag" href="/hashtags/a">#a</a>', '<br>', 'b', '<br>', '<a class="hashtag" href="/hashtags/c">#c</a>', '<br>', '<a class="index" href="#index::d">d</a>'], '']);
154
153
  assert.deepStrictEqual(inspect(parser('##a')), [['##a'], '']);
154
+ assert.deepStrictEqual(inspect(parser('_#a')), [['_', '<a class="hashtag" href="/hashtags/a">#a</a>'], '']);
155
+ assert.deepStrictEqual(inspect(parser('_#a_')), [['<em><a class="hashtag" href="/hashtags/a">#a</a></em>'], '']);
155
156
  assert.deepStrictEqual(inspect(parser('a#b')), [['a#b'], '']);
156
157
  assert.deepStrictEqual(inspect(parser('0a#b')), [['0a#b'], '']);
157
158
  assert.deepStrictEqual(inspect(parser('あ#b')), [['あ#b'], '']);
@@ -173,6 +174,8 @@ describe('Unit: parser/inline', () => {
173
174
  it('hashnum', () => {
174
175
  assert.deepStrictEqual(inspect(parser('#1')), [['<a class="hashnum">#1</a>'], '']);
175
176
  assert.deepStrictEqual(inspect(parser('#12345678901234567@a')), [['#12345678901234567@a'], '']);
177
+ assert.deepStrictEqual(inspect(parser('_#1_')), [['<em><a class="hashnum">#1</a></em>'], '']);
178
+ assert.deepStrictEqual(inspect(parser('_#1_0')), [['<em><a class="hashnum">#1</a></em>', '0'], '']);
176
179
  assert.deepStrictEqual(inspect(parser('「#1」')), [['「', '<a class="hashnum">#1</a>', '」'], '']);
177
180
  });
178
181
 
@@ -1,31 +0,0 @@
1
- import { horizontalrule } from './horizontalrule';
2
- import { some } from '../../combinator';
3
- import { inspect } from '../../debug.test';
4
-
5
- describe('Unit: parser/block/horizontalrule', () => {
6
- describe('horizontalrule', () => {
7
- const parser = (source: string) => some(horizontalrule)({ source, context: {} });
8
-
9
- it('invalid', () => {
10
- assert.deepStrictEqual(inspect(parser('')), undefined);
11
- assert.deepStrictEqual(inspect(parser('\n')), undefined);
12
- assert.deepStrictEqual(inspect(parser('-')), undefined);
13
- assert.deepStrictEqual(inspect(parser('--')), undefined);
14
- assert.deepStrictEqual(inspect(parser('--\n-')), undefined);
15
- assert.deepStrictEqual(inspect(parser('---a')), undefined);
16
- assert.deepStrictEqual(inspect(parser('---\na')), undefined);
17
- assert.deepStrictEqual(inspect(parser('- - -')), undefined);
18
- assert.deepStrictEqual(inspect(parser(' ---')), undefined);
19
- assert.deepStrictEqual(inspect(parser('***')), undefined);
20
- });
21
-
22
- it('valid', () => {
23
- assert.deepStrictEqual(inspect(parser('---')), [['<hr>'], '']);
24
- assert.deepStrictEqual(inspect(parser('--- ')), [['<hr>'], '']);
25
- assert.deepStrictEqual(inspect(parser('---\n')), [['<hr>'], '']);
26
- assert.deepStrictEqual(inspect(parser('----')), [['<hr>'], '']);
27
- });
28
-
29
- });
30
-
31
- });
@@ -1,7 +0,0 @@
1
- import { HorizontalRuleParser } from '../block';
2
- import { block, line, focus } from '../../combinator';
3
- import { html } from 'typed-dom/dom';
4
-
5
- export const horizontalrule: HorizontalRuleParser = block(line(focus(
6
- /^-{3,}[^\S\n]*(?:$|\n)/,
7
- () => [[html('hr')], ''])));