securemark 0.260.5 → 0.260.6

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.260.6
4
+
5
+ - Refactoring.
6
+
3
7
  ## 0.260.5
4
8
 
5
9
  - Refactoring.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! securemark v0.260.5 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
1
+ /*! securemark v0.260.6 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("DOMPurify"), require("Prism"));
@@ -64,9 +64,9 @@ __exportStar(__webpack_require__(256), exports);
64
64
  Object.defineProperty(exports, "__esModule", ({
65
65
  value: true
66
66
  }));
67
- exports.ObjectSetPrototypeOf = exports.ObjectGetPrototypeOf = exports.ObjectCreate = exports.ObjectAssign = exports.toString = exports.isEnumerable = exports.isPrototypeOf = exports.hasOwnProperty = exports.isArray = exports.sign = exports.round = exports.random = exports.min = exports.max = exports.floor = exports.ceil = exports.abs = exports.parseInt = exports.parseFloat = exports.isSafeInteger = exports.isNaN = exports.isInteger = exports.isFinite = exports[NaN] = void 0;
67
+ exports.ObjectSetPrototypeOf = exports.ObjectGetPrototypeOf = exports.ObjectCreate = exports.ObjectAssign = exports.toString = exports.isEnumerable = exports.isPrototypeOf = exports.hasOwnProperty = exports.isArray = exports.sqrt = exports.log = exports.tan = exports.cos = exports.sign = exports.round = exports.random = exports.min = exports.max = exports.floor = exports.ceil = exports.abs = exports.parseInt = exports.parseFloat = exports.isSafeInteger = exports.isNaN = exports.isInteger = exports.isFinite = exports[NaN] = void 0;
68
68
  exports[NaN] = Number.NaN, exports.isFinite = Number.isFinite, exports.isInteger = Number.isInteger, exports.isNaN = Number.isNaN, exports.isSafeInteger = Number.isSafeInteger, exports.parseFloat = Number.parseFloat, exports.parseInt = Number.parseInt;
69
- exports.abs = Math.abs, exports.ceil = Math.ceil, exports.floor = Math.floor, exports.max = Math.max, exports.min = Math.min, exports.random = Math.random, exports.round = Math.round, exports.sign = Math.sign;
69
+ exports.abs = Math.abs, exports.ceil = Math.ceil, exports.floor = Math.floor, exports.max = Math.max, exports.min = Math.min, exports.random = Math.random, exports.round = Math.round, exports.sign = Math.sign, exports.cos = Math.cos, exports.tan = Math.tan, exports.log = Math.log, exports.sqrt = Math.sqrt;
70
70
  exports.isArray = Array.isArray;
71
71
  exports.hasOwnProperty = Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty);
72
72
  exports.isPrototypeOf = Object.prototype.isPrototypeOf.call.bind(Object.prototype.isPrototypeOf);
@@ -356,6 +356,8 @@ exports.Cache = void 0;
356
356
 
357
357
  const global_1 = __webpack_require__(4128);
358
358
 
359
+ const alias_1 = __webpack_require__(5406);
360
+
359
361
  const clock_1 = __webpack_require__(7681);
360
362
 
361
363
  const invlist_1 = __webpack_require__(7452);
@@ -364,20 +366,23 @@ const heap_1 = __webpack_require__(818);
364
366
 
365
367
  const assign_1 = __webpack_require__(4401);
366
368
 
367
- const tuple_1 = __webpack_require__(5341);
368
-
369
369
  class Cache {
370
370
  constructor(capacity, opts = {}) {
371
371
  this.settings = {
372
+ window: 0,
372
373
  capacity: 0,
373
374
  space: global_1.Infinity,
374
375
  age: global_1.Infinity,
375
376
  earlyExpiring: false,
376
- limit: 950,
377
377
  capture: {
378
378
  delete: true,
379
379
  clear: true
380
- }
380
+ },
381
+ resolution: 1,
382
+ offset: 0,
383
+ block: 20,
384
+ sweep: 10,
385
+ limit: 950
381
386
  };
382
387
  this.overlap = 0;
383
388
  this.SIZE = 0;
@@ -387,31 +392,8 @@ class Cache {
387
392
  LFU: new invlist_1.List()
388
393
  };
389
394
  this.expiries = new heap_1.Heap((a, b) => a.value.expiry - b.value.expiry);
390
- this.stats = {
391
- LRU: (0, tuple_1.tuple)(0, 0),
392
- LFU: (0, tuple_1.tuple)(0, 0),
393
-
394
- slide() {
395
- const {
396
- LRU,
397
- LFU
398
- } = this;
399
- LRU[1] = LRU[0];
400
- LRU[0] = 0;
401
- LFU[1] = LFU[0];
402
- LFU[0] = 0;
403
- },
404
-
405
- clear() {
406
- const {
407
- LRU,
408
- LFU
409
- } = this;
410
- LRU[0] = LRU[1] = 0;
411
- LFU[0] = LFU[1] = 0;
412
- }
413
-
414
- };
395
+ this.misses = 0;
396
+ this.sweep = 0;
415
397
  this.ratio = 500;
416
398
 
417
399
  if (typeof capacity === 'object') {
@@ -419,15 +401,19 @@ class Cache {
419
401
  capacity = opts.capacity ?? 0;
420
402
  }
421
403
 
422
- (0, assign_1.extend)(this.settings, opts, {
404
+ const settings = (0, assign_1.extend)(this.settings, opts, {
423
405
  capacity
424
406
  });
425
- this.capacity = this.settings.capacity;
407
+ this.capacity = settings.capacity;
426
408
  if (this.capacity >= 1 === false) throw new Error(`Spica: Cache: Capacity must be 1 or more.`);
427
- this.space = this.settings.space;
428
- this.limit = this.settings.limit;
429
- this.earlyExpiring = this.settings.earlyExpiring;
430
- this.disposer = this.settings.disposer;
409
+ this.window = settings.window || this.capacity;
410
+ if (this.window * 1000 < this.capacity) throw new Error(`Spica: Cache: Window must be 0.1% of capacity or more.`);
411
+ this.space = settings.space;
412
+ this.block = settings.block;
413
+ this.limit = settings.limit;
414
+ this.earlyExpiring = settings.earlyExpiring;
415
+ this.disposer = settings.disposer;
416
+ this.stats = new Stats(this.window, settings.resolution, settings.offset);
431
417
  }
432
418
 
433
419
  get length() {
@@ -452,7 +438,7 @@ class Cache {
452
438
 
453
439
  ensure(margin, skip) {
454
440
  let size = skip?.value.size ?? 0;
455
- if (margin - size <= 0) return;
441
+ if (margin - size <= 0) return true;
456
442
  const {
457
443
  LRU,
458
444
  LFU
@@ -474,7 +460,6 @@ class Cache {
474
460
  target = LFU.last !== skip ? LFU.last : LFU.length >= 2 ? LFU.last.prev : skip;
475
461
 
476
462
  if (target !== skip) {
477
- if (this.ratio > 500) break;
478
463
  LRU.unshiftNode(target);
479
464
  ++this.overlap;
480
465
  }
@@ -482,6 +467,18 @@ class Cache {
482
467
  // fallthrough
483
468
 
484
469
  default:
470
+ if (this.misses * 100 > LRU.length * this.block) {
471
+ this.sweep ||= LRU.length * this.settings.sweep / 100 + 1 | 0;
472
+
473
+ if (this.sweep > 0) {
474
+ LRU.head = LRU.head.next.next;
475
+ --this.sweep;
476
+ this.sweep ||= -(0, alias_1.round)(LRU.length * this.settings.sweep / 100 * 99 / 100);
477
+ } else {
478
+ ++this.sweep;
479
+ }
480
+ }
481
+
485
482
  target = LRU.last !== skip ? LRU.last : LRU.length >= 2 ? LRU.last.prev : LFU.last;
486
483
  }
487
484
 
@@ -489,6 +486,8 @@ class Cache {
489
486
  skip = skip?.list && skip;
490
487
  size = skip?.value.size ?? 0;
491
488
  }
489
+
490
+ return !!skip?.list;
492
491
  }
493
492
 
494
493
  put(key, value, {
@@ -503,10 +502,9 @@ class Cache {
503
502
  const expiry = age === global_1.Infinity ? global_1.Infinity : (0, clock_1.now)() + age;
504
503
  const node = this.memory.get(key);
505
504
 
506
- if (node) {
505
+ if (node && this.ensure(size, node)) {
507
506
  const val = node.value.value;
508
507
  const index = node.value;
509
- this.ensure(size, node);
510
508
  this.SIZE += size - index.size;
511
509
  index.size = size;
512
510
  index.expiry = expiry;
@@ -550,10 +548,16 @@ class Cache {
550
548
 
551
549
  get(key) {
552
550
  const node = this.memory.get(key);
553
- if (!node) return;
551
+
552
+ if (!node) {
553
+ ++this.misses;
554
+ return;
555
+ }
556
+
554
557
  const expiry = node.value.expiry;
555
558
 
556
559
  if (expiry !== global_1.Infinity && expiry < (0, clock_1.now)()) {
560
+ ++this.misses;
557
561
  this.evict(node, true);
558
562
  return;
559
563
  } // Optimization for memoize.
@@ -561,7 +565,9 @@ class Cache {
561
565
 
562
566
  if (this.capacity > 3 && node === node.list.head) return node.value.value;
563
567
  this.access(node);
564
- this.slide();
568
+ this.adjust();
569
+ this.misses &&= 0;
570
+ this.sweep = 0;
565
571
  return node.value.value;
566
572
  }
567
573
 
@@ -588,6 +594,8 @@ class Cache {
588
594
  }
589
595
 
590
596
  clear() {
597
+ this.misses = 0;
598
+ this.sweep = 0;
591
599
  this.overlap = 0;
592
600
  this.SIZE = 0;
593
601
  this.ratio = 500;
@@ -620,31 +628,26 @@ class Cache {
620
628
  return;
621
629
  }
622
630
 
623
- slide() {
624
- const {
625
- LRU,
626
- LFU
627
- } = this.stats;
631
+ adjust() {
628
632
  const {
629
633
  capacity,
630
634
  ratio,
631
635
  limit,
636
+ stats,
632
637
  indexes
633
638
  } = this;
634
- const window = capacity;
635
- const total = LRU[0] + LFU[0];
636
- total === window && this.stats.slide();
637
- if (total * 1000 % capacity || !LRU[1] || !LFU[1]) return;
639
+ if (stats.subtotal() * 1000 % capacity || !stats.full()) return;
638
640
  const lenR = indexes.LRU.length;
639
641
  const lenF = indexes.LFU.length;
640
- const lenV = this.overlap;
641
- const r = (lenF + lenV) * 1000 / (lenR + lenF || 1) | 0;
642
- const rateR0 = rate(window, LRU[0], LRU[0] + LFU[0], LRU[1], LRU[1] + LFU[1], 0) * r;
643
- const rateF0 = rate(window, LFU[0], LRU[0] + LFU[0], LFU[1], LRU[1] + LFU[1], 0) * (1000 - r);
644
- const rateF1 = rate(window, LFU[1], LRU[1] + LFU[1], LFU[0], LRU[0] + LFU[0], 5) * (1000 - r); // 操作頻度を超えてキャッシュ比率を増減させても余剰比率の消化が追いつかず無駄
642
+ const lenO = this.overlap;
643
+ const leverage = (lenF + lenO) * 1000 / (lenR + lenF) | 0;
644
+ const rateR0 = stats.rateLRU() * leverage;
645
+ const rateF0 = stats.rateLFU() * (1000 - leverage);
646
+ const rateF1 = stats.offset && stats.rateLFU(true) * (1000 - leverage); // 操作頻度を超えてキャッシュ比率を増減させても余剰比率の消化が追いつかず無駄
645
647
  // LRUの下限設定ではLRU拡大の要否を迅速に判定できないためLFUのヒット率低下の検出で代替する
646
648
 
647
- if (ratio > 0 && (rateR0 > rateF0 || rateF0 < rateF1 * 0.95)) {
649
+ if (ratio > 0 && (rateR0 > rateF0 || stats.offset && rateF0 * 100 < rateF1 * (100 - stats.offset))) {
650
+ //rateR0 <= rateF0 && rateF0 * 100 < rateF1 * (100 - stats.offset) && console.debug(0);
648
651
  if (lenR >= capacity * (1000 - ratio) / 1000) {
649
652
  //ratio % 100 || ratio === 1000 || console.debug('-', ratio, LRU, LFU);
650
653
  --this.ratio;
@@ -682,13 +685,100 @@ class Cache {
682
685
 
683
686
  exports.Cache = Cache;
684
687
 
685
- function rate(window, currHits, currTotal, prevHits, prevTotal, offset) {
686
- const prevRate = prevHits * 100 / prevTotal | 0;
687
- const currRatio = currTotal * 100 / window - offset | 0;
688
- if (currRatio <= 0) return prevRate * 100;
689
- const currRate = currHits * 100 / currTotal | 0;
690
- const prevRatio = 100 - currRatio;
691
- return currRate * currRatio + prevRate * prevRatio;
688
+ class Stats {
689
+ constructor(window, resolution, offset) {
690
+ this.window = window;
691
+ this.resolution = resolution;
692
+ this.offset = offset;
693
+ this.max = (0, alias_1.ceil)(this.resolution * (100 + this.offset) / 100) + 1;
694
+ this.LRU = [0];
695
+ this.LFU = [0];
696
+ }
697
+
698
+ get length() {
699
+ return this.LRU.length;
700
+ }
701
+
702
+ full() {
703
+ return this.length === this.max;
704
+ }
705
+
706
+ rateLRU(offset = false) {
707
+ return rate(this.window, this.LRU, this.LFU, +offset && this.offset);
708
+ }
709
+
710
+ rateLFU(offset = false) {
711
+ return rate(this.window, this.LFU, this.LRU, +offset && this.offset);
712
+ }
713
+
714
+ subtotal() {
715
+ const {
716
+ LRU,
717
+ LFU,
718
+ window,
719
+ resolution,
720
+ offset
721
+ } = this;
722
+
723
+ if (offset && LRU[0] + LFU[0] >= window * offset / 100) {
724
+ if (this.length === 1) {
725
+ this.slide();
726
+ } else {
727
+ LRU[1] += LRU[0];
728
+ LFU[1] += LFU[0];
729
+ LRU[0] = 0;
730
+ LFU[0] = 0;
731
+ }
732
+ }
733
+
734
+ const subtotal = LRU[+offset && 1] + LFU[+offset && 1] || 0;
735
+ subtotal >= window / resolution && this.slide();
736
+ return LRU[0] + LFU[0];
737
+ }
738
+
739
+ slide() {
740
+ const {
741
+ LRU,
742
+ LFU,
743
+ max
744
+ } = this;
745
+
746
+ if (LRU.length === max) {
747
+ LRU.pop();
748
+ LFU.pop();
749
+ }
750
+
751
+ LRU.unshift(0);
752
+ LFU.unshift(0);
753
+ }
754
+
755
+ clear() {
756
+ this.LRU = [0];
757
+ this.LFU = [0];
758
+ }
759
+
760
+ }
761
+
762
+ function rate(window, hits1, hits2, offset) {
763
+ let total = 0;
764
+ let hits = 0;
765
+ let ratio = 100;
766
+
767
+ for (let i = 0, len = hits1.length; i < len; ++i) {
768
+ const subtotal = hits1[i] + hits2[i];
769
+ if (subtotal === 0) continue;
770
+ offset = i + 1 === len ? 0 : offset;
771
+ const subratio = (0, alias_1.min)(subtotal * 100 / window, ratio) - offset;
772
+ offset = offset && subratio < 0 ? -subratio : 0;
773
+ if (subratio <= 0) continue;
774
+ const rate = window * subratio / subtotal;
775
+ total += subtotal * rate;
776
+ hits += hits1[i] * rate;
777
+ ratio -= subratio;
778
+ if (ratio <= 0) break;
779
+ }
780
+
781
+ return hits * 10000 / total | 0;
692
782
  }
693
783
 
694
784
  /***/ }),
@@ -1671,25 +1761,6 @@ function random(len) {
1671
1761
 
1672
1762
  /***/ }),
1673
1763
 
1674
- /***/ 5341:
1675
- /***/ ((__unused_webpack_module, exports) => {
1676
-
1677
- "use strict";
1678
-
1679
-
1680
- Object.defineProperty(exports, "__esModule", ({
1681
- value: true
1682
- }));
1683
- exports.tuple = void 0;
1684
-
1685
- function tuple(...as) {
1686
- return as;
1687
- }
1688
-
1689
- exports.tuple = tuple;
1690
-
1691
- /***/ }),
1692
-
1693
1764
  /***/ 5177:
1694
1765
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
1695
1766
 
@@ -5502,15 +5573,16 @@ const source_1 = __webpack_require__(6743);
5502
5573
 
5503
5574
  const dom_1 = __webpack_require__(3252);
5504
5575
 
5505
- exports.cite = (0, combinator_1.creation)(1, false, (0, combinator_1.line)((0, combinator_1.fmap)((0, combinator_1.validate)('>>', (0, combinator_1.reverse)((0, combinator_1.tails)([(0, source_1.str)(/^>*(?=>>[^>\s]+[^\S\n]*(?:$|\n))/), (0, combinator_1.union)([anchor_1.anchor, // Subject page representation.
5576
+ exports.cite = (0, combinator_1.creation)(1, false, (0, combinator_1.line)((0, combinator_1.fmap)((0, combinator_1.validate)('>>', (0, combinator_1.reverse)((0, combinator_1.tails)([(0, source_1.str)(/^>*(?=>>[^>\s]+\s*$)/), (0, combinator_1.union)([anchor_1.anchor, // Subject page representation.
5506
5577
  // リンクの実装は後で検討
5507
- (0, combinator_1.focus)(/^>>\.[^\S\n]*(?:$|\n)/, () => [[(0, dom_1.html)('a', {
5578
+ (0, combinator_1.focus)(/^>>\.(?=\s*$)/, () => [[(0, dom_1.html)('a', {
5508
5579
  class: 'anchor'
5509
- }, '>>.')], '']), (0, combinator_1.focus)(/^>>#\S*[^\S\n]*(?:$|\n)/, ({
5580
+ }, '>>.')], '']), (0, combinator_1.focus)(/^>>#\S*(?=\s*$)/, ({
5510
5581
  source
5511
5582
  }) => [[(0, dom_1.html)('a', {
5512
5583
  class: 'anchor'
5513
- }, source)], '']), (0, combinator_1.focus)(/^>>https?:\/\/\w\S*[^\S\n]*(?:$|\n)/, ({
5584
+ }, source)], '']), // Support all domains, but don't support IP(v6) addresses.
5585
+ (0, combinator_1.focus)(/^>>https?:\/\/[^\p{C}\p{S}\p{P}\s]\S*(?=\s*$)/u, ({
5514
5586
  source
5515
5587
  }) => [[(0, dom_1.html)('a', {
5516
5588
  class: 'anchor',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.260.5",
3
+ "version": "0.260.6",
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",
@@ -34,7 +34,7 @@
34
34
  "@types/mocha": "9.1.1",
35
35
  "@types/power-assert": "1.5.8",
36
36
  "@types/prismjs": "1.26.0",
37
- "@typescript-eslint/parser": "^5.30.7",
37
+ "@typescript-eslint/parser": "^5.31.0",
38
38
  "babel-loader": "^8.2.5",
39
39
  "babel-plugin-unassert": "^3.2.0",
40
40
  "concurrently": "^7.3.0",
@@ -49,13 +49,13 @@
49
49
  "karma-mocha": "^2.0.1",
50
50
  "karma-power-assert": "^1.0.0",
51
51
  "mocha": "^10.0.0",
52
- "npm-check-updates": "^15.3.4",
52
+ "npm-check-updates": "^16.0.3",
53
53
  "semver": "^7.3.7",
54
- "spica": "0.0.573",
54
+ "spica": "0.0.591",
55
55
  "ts-loader": "^9.3.1",
56
56
  "typed-dom": "^0.0.301",
57
57
  "typescript": "4.7.4",
58
- "webpack": "^5.73.0",
58
+ "webpack": "^5.74.0",
59
59
  "webpack-cli": "^4.10.0",
60
60
  "webpack-merge": "^5.8.0"
61
61
  },
@@ -39,11 +39,16 @@ describe('Unit: parser/block/reply/cite', () => {
39
39
  assert.deepStrictEqual(inspect(parser('>>>0\n>>')), [['<span class="cite">&gt;&gt;<a class="anchor" href="?at=0" data-depth="2">&gt;0</a></span>', '<br>'], '>>']);
40
40
  assert.deepStrictEqual(inspect(parser('>>>0\n>>1')), [['<span class="cite">&gt;&gt;<a class="anchor" href="?at=0" data-depth="2">&gt;0</a></span>', '<br>', '<span class="cite">&gt;<a class="anchor" href="?at=1" data-depth="1">&gt;1</a></span>', '<br>'], '']);
41
41
  assert.deepStrictEqual(inspect(parser('>>.')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;.</a></span>', '<br>'], '']);
42
+ assert.deepStrictEqual(inspect(parser('>>. ')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;.</a></span>', '<br>'], '']);
43
+ assert.deepStrictEqual(inspect(parser('>>.\n')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;.</a></span>', '<br>'], '']);
42
44
  assert.deepStrictEqual(inspect(parser('>>#')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#</a></span>', '<br>'], '']);
45
+ assert.deepStrictEqual(inspect(parser('>>#\n')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#</a></span>', '<br>'], '']);
43
46
  assert.deepStrictEqual(inspect(parser('>>#a')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#a</a></span>', '<br>'], '']);
44
47
  assert.deepStrictEqual(inspect(parser('>>#index:a')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#index:a</a></span>', '<br>'], '']);
45
48
  assert.deepStrictEqual(inspect(parser('>>#:~:text=a')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#:~:text=a</a></span>', '<br>'], '']);
46
49
  assert.deepStrictEqual(inspect(parser('>>http://host')), [['<span class="cite">&gt;<a class="anchor" href="http://host" target="_blank" data-depth="1">&gt;http://host</a></span>', '<br>'], '']);
50
+ assert.deepStrictEqual(inspect(parser('>>http://host ')), [['<span class="cite">&gt;<a class="anchor" href="http://host" target="_blank" data-depth="1">&gt;http://host</a></span>', '<br>'], '']);
51
+ assert.deepStrictEqual(inspect(parser('>>http://host\n')), [['<span class="cite">&gt;<a class="anchor" href="http://host" target="_blank" data-depth="1">&gt;http://host</a></span>', '<br>'], '']);
47
52
  assert.deepStrictEqual(inspect(parser('>>https://host')), [['<span class="cite">&gt;<a class="anchor" href="https://host" target="_blank" data-depth="1">&gt;https://host</a></span>', '<br>'], '']);
48
53
  });
49
54
 
@@ -7,14 +7,15 @@ import { html, define, defrag } from 'typed-dom/dom';
7
7
  export const cite: ReplyParser.CiteParser = creation(1, false, line(fmap(validate(
8
8
  '>>',
9
9
  reverse(tails([
10
- str(/^>*(?=>>[^>\s]+[^\S\n]*(?:$|\n))/),
10
+ str(/^>*(?=>>[^>\s]+\s*$)/),
11
11
  union([
12
12
  anchor,
13
13
  // Subject page representation.
14
14
  // リンクの実装は後で検討
15
- focus(/^>>\.[^\S\n]*(?:$|\n)/, () => [[html('a', { class: 'anchor' }, '>>.')], '']),
16
- focus(/^>>#\S*[^\S\n]*(?:$|\n)/, ({ source }) => [[html('a', { class: 'anchor' }, source)], '']),
17
- focus(/^>>https?:\/\/\w\S*[^\S\n]*(?:$|\n)/, ({ source }) => [[html('a', { class: 'anchor', href: source.slice(2).trimEnd(), target: '_blank' }, source)], '']),
15
+ focus(/^>>\.(?=\s*$)/, () => [[html('a', { class: 'anchor' }, '>>.')], '']),
16
+ focus(/^>>#\S*(?=\s*$)/, ({ source }) => [[html('a', { class: 'anchor' }, source)], '']),
17
+ // Support all domains, but don't support IP(v6) addresses.
18
+ focus(/^>>https?:\/\/[^\p{C}\p{S}\p{P}\s]\S*(?=\s*$)/u, ({ source }) => [[html('a', { class: 'anchor', href: source.slice(2).trimEnd(), target: '_blank' }, source)], '']),
18
19
  ]),
19
20
  ]))),
20
21
  ([el, quotes = '']: [HTMLElement, string?]) => [