securemark 0.230.1 → 0.231.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.231.0
4
+
5
+ - Refine hashtag syntax.
6
+
3
7
  ## 0.230.1
4
8
 
5
9
  - Refactoring.
@@ -1,4 +1,4 @@
1
- /*! securemark v0.230.1 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED */
1
+ /*! securemark v0.231.0 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED */
2
2
  require = function () {
3
3
  function r(e, n, t) {
4
4
  function o(i, f) {
@@ -6257,10 +6257,10 @@ require = function () {
6257
6257
  channel_1.channel,
6258
6258
  account_1.account,
6259
6259
  (0, source_1.str)(/^@+[0-9A-Za-z]*(?:-[0-9A-Za-z]+)*/),
6260
- (0, source_1.str)(/^(?:[0-9A-Za-z]|[^\x00-\x7F\s])(?=#)/u),
6260
+ (0, source_1.str)(new RegExp(String.raw`^(?:[^\p{C}\p{S}\p{P}\s]|${ hashtag_1.emoji }|['_])(?=#)`, 'u')),
6261
6261
  hashtag_1.hashtag,
6262
6262
  hashnum_1.hashnum,
6263
- (0, source_1.str)(/^#+(?:[0-9A-Za-z'_]|[^\x00-\x7F\s])*/),
6263
+ (0, source_1.str)(new RegExp(String.raw`^#+(?:[^\p{C}\p{S}\p{P}\s]|${ hashtag_1.emoji }|['_])*`, 'u')),
6264
6264
  anchor_1.anchor
6265
6265
  ])))), ns => ns.length === 1 ? ns : [(0, util_1.stringify)(ns)]);
6266
6266
  },
@@ -6287,7 +6287,7 @@ require = function () {
6287
6287
  const source_1 = _dereq_('../../source');
6288
6288
  const typed_dom_1 = _dereq_('typed-dom');
6289
6289
  exports.account = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.open)('@', (0, combinator_1.tails)([
6290
- (0, combinator_1.verify)((0, source_1.str)(/^[0-9A-Za-z](?:[0-9A-Za-z-]{0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:[0-9A-Za-z-]{0,61}[0-9A-Za-z])?)*\//), ([source]) => source.length <= 253 + 1),
6290
+ (0, combinator_1.verify)((0, source_1.str)(/^[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*\//), ([source]) => source.length <= 253 + 1),
6291
6291
  (0, combinator_1.verify)((0, source_1.str)(/^[A-Za-z][0-9A-Za-z]*(?:-[0-9A-Za-z]+)*/), ([source]) => source.length <= 64)
6292
6292
  ])), (0, combinator_1.context)({
6293
6293
  syntax: {
@@ -6374,7 +6374,7 @@ require = function () {
6374
6374
  const combinator_1 = _dereq_('../../../combinator');
6375
6375
  const source_1 = _dereq_('../../source');
6376
6376
  const typed_dom_1 = _dereq_('typed-dom');
6377
- exports.email = (0, combinator_1.creator)((0, combinator_1.rewrite)((0, combinator_1.verify)((0, source_1.str)(/^[0-9A-Za-z]+(?:[.+_-][0-9A-Za-z]+)*@[0-9A-Za-z](?:[0-9A-Za-z-]{0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:[0-9A-Za-z-]{0,61}[0-9A-Za-z])?)*/), ([source]) => source.indexOf('@') <= 64 && source.length <= 255), source => [
6377
+ exports.email = (0, combinator_1.creator)((0, combinator_1.rewrite)((0, combinator_1.verify)((0, source_1.str)(/^[0-9A-Za-z]+(?:[.+_-][0-9A-Za-z]+)*@[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*(?![0-9A-Za-z])/), ([source]) => source.indexOf('@') <= 64 && source.length <= 255), source => [
6378
6378
  [(0, typed_dom_1.html)('a', {
6379
6379
  class: 'email',
6380
6380
  href: `mailto:${ source }`
@@ -6395,9 +6395,10 @@ require = function () {
6395
6395
  exports.hashnum = void 0;
6396
6396
  const combinator_1 = _dereq_('../../../combinator');
6397
6397
  const link_1 = _dereq_('../link');
6398
+ const hashtag_1 = _dereq_('./hashtag');
6398
6399
  const source_1 = _dereq_('../../source');
6399
6400
  const typed_dom_1 = _dereq_('typed-dom');
6400
- exports.hashnum = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.open)('#', (0, source_1.str)(/^[0-9]{1,16}(?![0-9A-Za-z'_]|[^\x00-\x7F\s])/)), (0, combinator_1.context)({
6401
+ exports.hashnum = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.open)('#', (0, source_1.str)(new RegExp(String.raw`^[0-9]{1,16}(?![^\p{C}\p{S}\p{P}\s]|${ hashtag_1.emoji }|['_])`, 'u'))), (0, combinator_1.context)({
6401
6402
  syntax: {
6402
6403
  inline: {
6403
6404
  link: true,
@@ -6413,6 +6414,7 @@ require = function () {
6413
6414
  '../../../combinator': 27,
6414
6415
  '../../source': 128,
6415
6416
  '../link': 114,
6417
+ './hashtag': 96,
6416
6418
  'typed-dom': 26
6417
6419
  }
6418
6420
  ],
@@ -6420,14 +6422,20 @@ require = function () {
6420
6422
  function (_dereq_, module, exports) {
6421
6423
  'use strict';
6422
6424
  Object.defineProperty(exports, '__esModule', { value: true });
6423
- exports.hashtag = void 0;
6425
+ exports.hashtag = exports.emoji = void 0;
6424
6426
  const combinator_1 = _dereq_('../../../combinator');
6425
6427
  const link_1 = _dereq_('../link');
6426
6428
  const source_1 = _dereq_('../../source');
6427
6429
  const typed_dom_1 = _dereq_('typed-dom');
6430
+ exports.emoji = String.raw`\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F`;
6428
6431
  exports.hashtag = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.open)('#', (0, combinator_1.tails)([
6429
- (0, combinator_1.verify)((0, source_1.str)(/^[0-9A-Za-z](?:[0-9A-Za-z-]{0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:[0-9A-Za-z-]{0,61}[0-9A-Za-z])?)*\//), ([source]) => source.length <= 253 + 1),
6430
- (0, combinator_1.verify)((0, source_1.str)(/^(?=(?:[0-9]{1,127}_?)?(?:[A-Za-z]|[^\x00-\x7F\s]))(?:[0-9A-Za-z]|[^\x00-\x7F\s]|'(?!')|_(?=[0-9A-Za-z]|[^\x00-\x7F\s])){1,128}(?:_?\((?=(?:[0-9]{1,127}_?)?(?:[A-Za-z]|[^\x00-\x7F\s]))(?:[0-9A-Za-z]|[^\x00-\x7F\s]|'(?!')|_(?=[0-9A-Za-z]|[^\x00-\x7F\s])){1,125}\))?(?![0-9A-Za-z'_]|[^\x00-\x7F\s])/), ([source]) => source.length <= 128)
6432
+ (0, combinator_1.verify)((0, source_1.str)(/^[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*\//), ([source]) => source.length <= 253 + 1),
6433
+ (0, combinator_1.verify)((0, source_1.str)(new RegExp([
6434
+ '^',
6435
+ String.raw`(?=[0-9]{0,127}_?(?:[^\d\p{C}\p{S}\p{P}\s]|${ exports.emoji }))`,
6436
+ String.raw`(?:[^\p{C}\p{S}\p{P}\s]|${ exports.emoji }|_(?=[^\p{C}\p{S}\p{P}\s]|${ exports.emoji })){1,128}`,
6437
+ String.raw`(?!_?(?:[^\p{C}\p{S}\p{P}\s]|${ exports.emoji })|')`
6438
+ ].join(''), 'u')), ([source]) => source.length <= 128)
6431
6439
  ])), (0, combinator_1.context)({
6432
6440
  syntax: {
6433
6441
  inline: {
@@ -7322,7 +7330,7 @@ require = function () {
7322
7330
  switch (true) {
7323
7331
  case uri.slice(0, 2) === '^/':
7324
7332
  const last = host.pathname.slice(host.pathname.lastIndexOf('/') + 1);
7325
- return last.includes('.') && /^[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) }`;
7333
+ return last.includes('.') && /^[0-9]*[A-Za-z][0-9A-Za-z]*$/.test(last.slice(last.lastIndexOf('.') + 1)) ? `${ host.pathname.slice(0, -last.length) }${ uri.slice(2) }` : `${ host.pathname.replace(/\/?$/, '/') }${ uri.slice(2) }`;
7326
7334
  case host.origin === source.origin && host.pathname === source.pathname:
7327
7335
  case uri.slice(0, 2) === '//':
7328
7336
  return uri;
package/package-lock.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.230.1",
3
+ "version": "0.231.0",
4
4
  "lockfileVersion": 1,
5
5
  "requires": true,
6
6
  "dependencies": {
@@ -349,6 +349,12 @@
349
349
  "to-fast-properties": "^2.0.0"
350
350
  }
351
351
  },
352
+ "@colors/colors": {
353
+ "version": "1.5.0",
354
+ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
355
+ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
356
+ "dev": true
357
+ },
352
358
  "@gar/promisify": {
353
359
  "version": "1.1.3",
354
360
  "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@@ -2056,14 +2062,6 @@
2056
2062
  "dev": true,
2057
2063
  "requires": {
2058
2064
  "colors": "1.0.3"
2059
- },
2060
- "dependencies": {
2061
- "colors": {
2062
- "version": "1.0.3",
2063
- "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
2064
- "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=",
2065
- "dev": true
2066
- }
2067
2065
  }
2068
2066
  },
2069
2067
  "cliui": {
@@ -2164,9 +2162,9 @@
2164
2162
  "dev": true
2165
2163
  },
2166
2164
  "colors": {
2167
- "version": "1.4.0",
2168
- "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
2169
- "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
2165
+ "version": "1.0.3",
2166
+ "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
2167
+ "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=",
2170
2168
  "dev": true
2171
2169
  },
2172
2170
  "combine-source-map": {
@@ -2927,9 +2925,9 @@
2927
2925
  "dev": true
2928
2926
  },
2929
2927
  "electron-to-chromium": {
2930
- "version": "1.4.73",
2931
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.73.tgz",
2932
- "integrity": "sha512-RlCffXkE/LliqfA5m29+dVDPB2r72y2D2egMMfIy3Le8ODrxjuZNVo4NIC2yPL01N4xb4nZQLwzi6Z5tGIGLnA==",
2928
+ "version": "1.4.75",
2929
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.75.tgz",
2930
+ "integrity": "sha512-LxgUNeu3BVU7sXaKjUDD9xivocQLxFtq6wgERrutdY/yIOps3ODOZExK1jg8DTEg4U8TUCb5MLGeWFOYuxjF3Q==",
2933
2931
  "dev": true
2934
2932
  },
2935
2933
  "elliptic": {
@@ -5050,9 +5048,9 @@
5050
5048
  }
5051
5049
  },
5052
5050
  "has-symbols": {
5053
- "version": "1.0.2",
5054
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
5055
- "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
5051
+ "version": "1.0.3",
5052
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
5053
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
5056
5054
  "dev": true
5057
5055
  },
5058
5056
  "has-tostringtag": {
@@ -6110,15 +6108,15 @@
6110
6108
  "dev": true
6111
6109
  },
6112
6110
  "karma": {
6113
- "version": "6.3.16",
6114
- "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.16.tgz",
6115
- "integrity": "sha512-nEU50jLvDe5yvXqkEJRf8IuvddUkOY2x5Xc4WXHz6dxINgGDrgD2uqQWeVrJs4hbfNaotn+HQ1LZJ4yOXrL7xQ==",
6111
+ "version": "6.3.17",
6112
+ "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.17.tgz",
6113
+ "integrity": "sha512-2TfjHwrRExC8yHoWlPBULyaLwAFmXmxQrcuFImt/JsAsSZu1uOWTZ1ZsWjqQtWpHLiatJOHL5jFjXSJIgCd01g==",
6116
6114
  "dev": true,
6117
6115
  "requires": {
6116
+ "@colors/colors": "1.5.0",
6118
6117
  "body-parser": "^1.19.0",
6119
6118
  "braces": "^3.0.2",
6120
6119
  "chokidar": "^3.5.1",
6121
- "colors": "1.4.0",
6122
6120
  "connect": "^3.7.0",
6123
6121
  "di": "^0.0.1",
6124
6122
  "dom-serialize": "^2.2.1",
@@ -7456,9 +7454,9 @@
7456
7454
  }
7457
7455
  },
7458
7456
  "npm-check-updates": {
7459
- "version": "12.4.0",
7460
- "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-12.4.0.tgz",
7461
- "integrity": "sha512-X14H74M8SVFkStmP1NDOMh0OjLB3mU2dwUeM71zyITJHkm08MASwwTcydW6YuGcNW1RUlVq1cQY2yWijv4zKUQ==",
7457
+ "version": "12.5.0",
7458
+ "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-12.5.0.tgz",
7459
+ "integrity": "sha512-nrryXO9IZdJsAIXo8LdtllrsGiTDE4IMAod7fl1jd5C38tOdZZG/crNNii4IkctxltQRmK/ziZwsMDTlhszZXg==",
7462
7460
  "dev": true,
7463
7461
  "requires": {
7464
7462
  "chalk": "^4.1.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.230.1",
3
+ "version": "0.231.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",
@@ -11,7 +11,7 @@ export const account: AutolinkParser.AccountParser = lazy(() => fmap(rewrite(
11
11
  '@',
12
12
  tails([
13
13
  verify(
14
- str(/^[0-9A-Za-z](?:[0-9A-Za-z-]{0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:[0-9A-Za-z-]{0,61}[0-9A-Za-z])?)*\//),
14
+ str(/^[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*\//),
15
15
  ([source]) => source.length <= 253 + 1),
16
16
  verify(
17
17
  str(/^[A-Za-z][0-9A-Za-z]*(?:-[0-9A-Za-z]+)*/),
@@ -23,6 +23,7 @@ describe('Unit: parser/inline/autolink/email', () => {
23
23
  assert.deepStrictEqual(inspect(parser('a+@b')), undefined);
24
24
  assert.deepStrictEqual(inspect(parser('a..b@c')), undefined);
25
25
  assert.deepStrictEqual(inspect(parser('a++b@c')), undefined);
26
+ assert.deepStrictEqual(inspect(parser(`a@${'b'.repeat(64)}`)), [[`a@${'b'.repeat(64)}`], '']);
26
27
  assert.deepStrictEqual(inspect(parser(' a@b')), undefined);
27
28
  });
28
29
 
@@ -36,7 +37,7 @@ describe('Unit: parser/inline/autolink/email', () => {
36
37
  assert.deepStrictEqual(inspect(parser('a@b_c')), [['<a class="email" href="mailto:a@b">a@b</a>'], '_c']);
37
38
  assert.deepStrictEqual(inspect(parser('a@b-')), [['<a class="email" href="mailto:a@b">a@b</a>'], '-']);
38
39
  assert.deepStrictEqual(inspect(parser('a@b-c')), [['<a class="email" href="mailto:a@b-c">a@b-c</a>'], '']);
39
- assert.deepStrictEqual(inspect(parser('a@b--c')), [['<a class="email" href="mailto:a@b--c">a@b--c</a>'], '']);
40
+ assert.deepStrictEqual(inspect(parser('a@b--c')), [['<a class="email" href="mailto:a@b">a@b</a>'], '--c']);
40
41
  assert.deepStrictEqual(inspect(parser('a@b.')), [['<a class="email" href="mailto:a@b">a@b</a>'], '.']);
41
42
  assert.deepStrictEqual(inspect(parser('a@b.c')), [['<a class="email" href="mailto:a@b.c">a@b.c</a>'], '']);
42
43
  assert.deepStrictEqual(inspect(parser('a@b..c')), [['<a class="email" href="mailto:a@b">a@b</a>'], '..c']);
@@ -6,6 +6,6 @@ import { html } from 'typed-dom';
6
6
  // https://html.spec.whatwg.org/multipage/input.html
7
7
 
8
8
  export const email: AutolinkParser.EmailParser = creator(rewrite(verify(
9
- str(/^[0-9A-Za-z]+(?:[.+_-][0-9A-Za-z]+)*@[0-9A-Za-z](?:[0-9A-Za-z-]{0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:[0-9A-Za-z-]{0,61}[0-9A-Za-z])?)*/),
9
+ str(/^[0-9A-Za-z]+(?:[.+_-][0-9A-Za-z]+)*@[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*(?![0-9A-Za-z])/),
10
10
  ([source]) => source.indexOf('@') <= 64 && source.length <= 255),
11
11
  source => [[html('a', { class: 'email', href: `mailto:${source}` }, source)], '']));
@@ -35,6 +35,7 @@ describe('Unit: parser/inline/autolink/hashnum', () => {
35
35
  assert.deepStrictEqual(inspect(parser('あ#1')), [['あ#1'], '']);
36
36
  assert.deepStrictEqual(inspect(parser(' #1')), undefined);
37
37
  assert.deepStrictEqual(inspect(parser('#12345678901234567')), [['#12345678901234567'], '']);
38
+ assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(128)}a`)), [[`#${'1'.repeat(128)}a`], '']);
38
39
  });
39
40
 
40
41
  it('valid', () => {
@@ -1,11 +1,12 @@
1
1
  import { AutolinkParser } from '../../inline';
2
2
  import { union, rewrite, context, open, convert, fmap, lazy } from '../../../combinator';
3
3
  import { link } from '../link';
4
+ import { emoji } from './hashtag';
4
5
  import { str } from '../../source';
5
6
  import { define } from 'typed-dom';
6
7
 
7
8
  export const hashnum: AutolinkParser.HashnumParser = lazy(() => fmap(rewrite(
8
- open('#', str(/^[0-9]{1,16}(?![0-9A-Za-z'_]|[^\x00-\x7F\s])/)),
9
+ open('#', str(new RegExp(String.raw`^[0-9]{1,16}(?![^\p{C}\p{S}\p{P}\s]|${emoji}|['_])`, 'u'))),
9
10
  context({ syntax: { inline: {
10
11
  link: true,
11
12
  autolink: false,
@@ -23,9 +23,6 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
23
23
  assert.deepStrictEqual(inspect(parser(`#'`)), [[`#'`], '']);
24
24
  assert.deepStrictEqual(inspect(parser(`#a''`)), [[`#a''`], '']);
25
25
  assert.deepStrictEqual(inspect(parser('#_')), [['#_'], '']);
26
- assert.deepStrictEqual(inspect(parser('#a_')), [['#a_'], '']);
27
- assert.deepStrictEqual(inspect(parser('#a__b')), [['#a__b'], '']);
28
- assert.deepStrictEqual(inspect(parser(`#a_'b`)), [[`#a_'b`], '']);
29
26
  assert.deepStrictEqual(inspect(parser('#(a)')), [['#'], '(a)']);
30
27
  assert.deepStrictEqual(inspect(parser('#{}')), [['#'], '{}']);
31
28
  assert.deepStrictEqual(inspect(parser('#{{}')), [['#'], '{{}']);
@@ -38,6 +35,9 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
38
35
  assert.deepStrictEqual(inspect(parser('a##1')), [['a##1'], '']);
39
36
  assert.deepStrictEqual(inspect(parser('a##b')), [['a##b'], '']);
40
37
  assert.deepStrictEqual(inspect(parser('あ#b')), [['あ#b'], '']);
38
+ assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(127)}_`)), [[`#${'1'.repeat(127)}_`], '']);
39
+ assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(127)}_a`)), [[`#${'1'.repeat(127)}_a`], '']);
40
+ assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(127)}_'a`)), [[`#${'1'.repeat(127)}_'a`], '']);
41
41
  assert.deepStrictEqual(inspect(parser(' #a')), undefined);
42
42
  });
43
43
 
@@ -50,21 +50,22 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
50
50
  assert.deepStrictEqual(inspect(parser('#a\\\n')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], '\\\n']);
51
51
  assert.deepStrictEqual(inspect(parser('#a)')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], ')']);
52
52
  assert.deepStrictEqual(inspect(parser('#a(b')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], '(b']);
53
- assert.deepStrictEqual(inspect(parser('#a(b)')), [['<a href="/hashtags/a(b)" class="hashtag">#a(b)</a>'], '']);
54
- assert.deepStrictEqual(inspect(parser('#a((b))')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], '((b))']);
55
- assert.deepStrictEqual(inspect(parser(`#a'`)), [[`<a href="/hashtags/a'" class="hashtag">#a'</a>`], '']);
56
- assert.deepStrictEqual(inspect(parser(`#a(b')`)), [[`<a href="/hashtags/a(b')" class="hashtag">#a(b')</a>`], '']);
57
- assert.deepStrictEqual(inspect(parser(`#a(b'')`)), [[`<a href="/hashtags/a" class="hashtag">#a</a>`], `(b'')`]);
58
- assert.deepStrictEqual(inspect(parser(`#a('b)`)), [['<a href="/hashtags/a" class="hashtag">#a</a>'], `('b)`]);
53
+ assert.deepStrictEqual(inspect(parser('#a(b)')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], '(b)']);
54
+ assert.deepStrictEqual(inspect(parser('#a_')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], '_']);
59
55
  assert.deepStrictEqual(inspect(parser('#a_b')), [['<a href="/hashtags/a_b" class="hashtag">#a_b</a>'], '']);
60
- assert.deepStrictEqual(inspect(parser(`#a'_b`)), [[`<a href="/hashtags/a'_b" class="hashtag">#a'_b</a>`], '']);
61
- assert.deepStrictEqual(inspect(parser('#a(b_c)')), [['<a href="/hashtags/a(b_c)" class="hashtag">#a(b_c)</a>'], '']);
62
- assert.deepStrictEqual(inspect(parser('#a(b__c)')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], '(b__c)']);
56
+ assert.deepStrictEqual(inspect(parser(`#a_'b`)), [['<a href="/hashtags/a" class="hashtag">#a</a>'], `_'b`]);
57
+ assert.deepStrictEqual(inspect(parser('#a__b')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], '__b']);
63
58
  assert.deepStrictEqual(inspect(parser('#あ')), [['<a href="/hashtags/あ" class="hashtag">#あ</a>'], '']);
59
+ assert.deepStrictEqual(inspect(parser('#👩')), [['<a href="/hashtags/👩" class="hashtag">#👩</a>'], '']);
64
60
  assert.deepStrictEqual(inspect(parser('#1a')), [['<a href="/hashtags/1a" class="hashtag">#1a</a>'], '']);
65
61
  assert.deepStrictEqual(inspect(parser('#1あ')), [['<a href="/hashtags/1あ" class="hashtag">#1あ</a>'], '']);
62
+ assert.deepStrictEqual(inspect(parser('#1👩')), [['<a href="/hashtags/1👩" class="hashtag">#1👩</a>'], '']);
66
63
  assert.deepStrictEqual(inspect(parser('#domain/a')), [['<a href="https://domain/hashtags/a" target="_blank" class="hashtag">#domain/a</a>'], '']);
67
64
  assert.deepStrictEqual(inspect(parser('#domain.co.jp/a')), [['<a href="https://domain.co.jp/hashtags/a" target="_blank" class="hashtag">#domain.co.jp/a</a>'], '']);
65
+ // Reserved
66
+ assert.deepStrictEqual(inspect(parser(`#a'`)), [[`#a'`], '']);
67
+ assert.deepStrictEqual(inspect(parser(`#a'b`)), [[`#a'b`], '']);
68
+ assert.deepStrictEqual(inspect(parser(`#a'_b`)), [[`#a'_b`], '']);
68
69
  });
69
70
 
70
71
  });
@@ -6,15 +6,22 @@ import { define } from 'typed-dom';
6
6
 
7
7
  // https://example/hashtags/a must be a hashtag page or a redirect page going there.
8
8
 
9
+ // https://github.com/tc39/proposal-regexp-unicode-property-escapes#matching-emoji
10
+ export const emoji = String.raw`\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F`;
11
+
9
12
  export const hashtag: AutolinkParser.HashtagParser = lazy(() => fmap(rewrite(
10
13
  open(
11
14
  '#',
12
15
  tails([
13
16
  verify(
14
- str(/^[0-9A-Za-z](?:[0-9A-Za-z-]{0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:[0-9A-Za-z-]{0,61}[0-9A-Za-z])?)*\//),
17
+ str(/^[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*\//),
15
18
  ([source]) => source.length <= 253 + 1),
16
19
  verify(
17
- str(/^(?=(?:[0-9]{1,127}_?)?(?:[A-Za-z]|[^\x00-\x7F\s]))(?:[0-9A-Za-z]|[^\x00-\x7F\s]|'(?!')|_(?=[0-9A-Za-z]|[^\x00-\x7F\s])){1,128}(?:_?\((?=(?:[0-9]{1,127}_?)?(?:[A-Za-z]|[^\x00-\x7F\s]))(?:[0-9A-Za-z]|[^\x00-\x7F\s]|'(?!')|_(?=[0-9A-Za-z]|[^\x00-\x7F\s])){1,125}\))?(?![0-9A-Za-z'_]|[^\x00-\x7F\s])/),
20
+ str(new RegExp(['^',
21
+ String.raw`(?=[0-9]{0,127}_?(?:[^\d\p{C}\p{S}\p{P}\s]|${emoji}))`,
22
+ String.raw`(?:[^\p{C}\p{S}\p{P}\s]|${emoji}|_(?=[^\p{C}\p{S}\p{P}\s]|${emoji})){1,128}`,
23
+ String.raw`(?!_?(?:[^\p{C}\p{S}\p{P}\s]|${emoji})|')`,
24
+ ].join(''), 'u')),
18
25
  ([source]) => source.length <= 128),
19
26
  ])),
20
27
  context({ syntax: { inline: {
@@ -4,7 +4,7 @@ import { url } from './autolink/url';
4
4
  import { email } from './autolink/email';
5
5
  import { channel } from './autolink/channel';
6
6
  import { account } from './autolink/account';
7
- import { hashtag } from './autolink/hashtag';
7
+ import { hashtag, emoji } from './autolink/hashtag';
8
8
  import { hashnum } from './autolink/hashnum';
9
9
  import { anchor } from './autolink/anchor';
10
10
  import { str } from '../source';
@@ -23,11 +23,11 @@ export const autolink: AutolinkParser = fmap(
23
23
  // Escape unmatched account-like strings.
24
24
  str(/^@+[0-9A-Za-z]*(?:-[0-9A-Za-z]+)*/),
25
25
  // Escape invalid leading characters.
26
- str(/^(?:[0-9A-Za-z]|[^\x00-\x7F\s])(?=#)/u),
26
+ str(new RegExp(String.raw`^(?:[^\p{C}\p{S}\p{P}\s]|${emoji}|['_])(?=#)`, 'u')),
27
27
  hashtag,
28
28
  hashnum,
29
29
  // Escape unmatched hashtag-like strings.
30
- str(/^#+(?:[0-9A-Za-z'_]|[^\x00-\x7F\s])*/),
30
+ str(new RegExp(String.raw`^#+(?:[^\p{C}\p{S}\p{P}\s]|${emoji}|['_])*`, 'u')),
31
31
  anchor,
32
32
  ])))),
33
33
  ns => ns.length === 1 ? ns : [stringify(ns)]);
@@ -83,7 +83,7 @@ export function resolve(uri: string, host: URL | Location, source: URL | Locatio
83
83
  case uri.slice(0, 2) === '^/':
84
84
  const last = host.pathname.slice(host.pathname.lastIndexOf('/') + 1);
85
85
  return last.includes('.') // isFile
86
- && /^[0-9]*[a-z][0-9a-z]*$/i.test(last.slice(last.lastIndexOf('.') + 1))
86
+ && /^[0-9]*[A-Za-z][0-9A-Za-z]*$/.test(last.slice(last.lastIndexOf('.') + 1))
87
87
  ? `${host.pathname.slice(0, -last.length)}${uri.slice(2)}`
88
88
  : `${host.pathname.replace(/\/?$/, '/')}${uri.slice(2)}`;
89
89
  case host.origin === source.origin
@@ -173,6 +173,7 @@ describe('Unit: parser/inline', () => {
173
173
  assert.deepStrictEqual(inspect(parser('あい#b')), [['あ', 'い#b'], '']);
174
174
  assert.deepStrictEqual(inspect(parser('0aあ#b')), [['0a', 'あ#b'], '']);
175
175
  assert.deepStrictEqual(inspect(parser('0aあい#b')), [['0a', 'あ', 'い#b'], '']);
176
+ assert.deepStrictEqual(inspect(parser('「#あ」')), [['「', '<a href="/hashtags/あ" class="hashtag">#あ</a>', '」'], '']);
176
177
  assert.deepStrictEqual(inspect(parser('a\n#b')), [['a', '<br>', '<a href="/hashtags/b" class="hashtag">#b</a>'], '']);
177
178
  assert.deepStrictEqual(inspect(parser('a\\\n#b')), [['a', '<span class="linebreak"> </span>', '<a href="/hashtags/b" class="hashtag">#b</a>'], '']);
178
179
  assert.deepStrictEqual(inspect(parser('*a*#b')), [['<em>a</em>', '<a href="/hashtags/b" class="hashtag">#b</a>'], '']);
@@ -186,6 +187,7 @@ describe('Unit: parser/inline', () => {
186
187
  it('hashnum', () => {
187
188
  assert.deepStrictEqual(inspect(parser('#1')), [['<a class="hashnum">#1</a>'], '']);
188
189
  assert.deepStrictEqual(inspect(parser('#12345678901234567@a')), [['#12345678901234567@a'], '']);
190
+ assert.deepStrictEqual(inspect(parser('「#1」')), [['「', '<a class="hashnum">#1</a>', '」'], '']);
189
191
  });
190
192
 
191
193
  });