securemark 0.260.0 → 0.260.3

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/design.md +4 -0
  3. package/dist/index.js +105 -97
  4. package/package.json +5 -5
  5. package/src/combinator/data/parser/context.ts +6 -4
  6. package/src/parser/api/parse.test.ts +13 -13
  7. package/src/parser/block/blockquote.ts +3 -3
  8. package/src/parser/block/dlist.ts +2 -2
  9. package/src/parser/block/extension/table.ts +3 -3
  10. package/src/parser/block/ilist.ts +1 -1
  11. package/src/parser/block/olist.ts +1 -1
  12. package/src/parser/block/reply/cite.ts +1 -1
  13. package/src/parser/block/reply/quote.ts +1 -1
  14. package/src/parser/block/sidefence.ts +1 -1
  15. package/src/parser/block/table.ts +4 -4
  16. package/src/parser/block/ulist.ts +2 -2
  17. package/src/parser/block.ts +1 -1
  18. package/src/parser/inline/annotation.test.ts +1 -1
  19. package/src/parser/inline/autolink/account.ts +2 -4
  20. package/src/parser/inline/autolink/anchor.ts +1 -1
  21. package/src/parser/inline/autolink/email.test.ts +1 -0
  22. package/src/parser/inline/autolink/email.ts +2 -2
  23. package/src/parser/inline/autolink/hashtag.ts +1 -1
  24. package/src/parser/inline/autolink/url.ts +3 -5
  25. package/src/parser/inline/autolink.ts +3 -3
  26. package/src/parser/inline/link.test.ts +55 -50
  27. package/src/parser/inline/link.ts +67 -49
  28. package/src/parser/inline/reference.test.ts +1 -1
  29. package/src/parser/inline/ruby.ts +1 -1
  30. package/src/parser/inline.test.ts +11 -11
  31. package/src/parser/source/escapable.ts +1 -1
  32. package/src/parser/source/str.ts +4 -4
  33. package/src/parser/source/text.ts +2 -3
  34. package/src/parser/source/unescapable.ts +1 -1
  35. package/src/renderer/render/media/twitter.ts +7 -1
@@ -36,12 +36,6 @@ const textlink: LinkParser.TextLinkParser = lazy(() =>
36
36
  ])),
37
37
  ([params, content = []]: [string[], (HTMLElement | string)[]], rest, context) => {
38
38
  assert(!html('div', content).querySelector('a, .media, .annotation, .reference'));
39
- if (content.length !== 0 && trimNode(content).length === 0) return;
40
- for (let source = stringify(content); source;) {
41
- const result = autolink({ source, context });
42
- if (typeof eval(result, [])[0] === 'object') return;
43
- source = exec(result, '');
44
- }
45
39
  return parse(content, params, rest, context);
46
40
  }))));
47
41
 
@@ -91,42 +85,45 @@ function parse(
91
85
  ): Result<HTMLAnchorElement, MarkdownParser.Context> {
92
86
  assert(params.length > 0);
93
87
  assert(params.every(p => typeof p === 'string'));
88
+ if (content.length !== 0 && trimNode(content).length === 0) return;
89
+ content = defrag(content);
90
+ for (let source = stringify(content); source;) {
91
+ if (/^[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*:\/\/[^/?#]/i.test(source)) return;
92
+ const result = autolink({ source, context });
93
+ if (typeof eval(result, [])[0] === 'object') return;
94
+ source = exec(result, '');
95
+ }
94
96
  const INSECURE_URI = params.shift()!;
95
97
  assert(INSECURE_URI === INSECURE_URI.trim());
96
98
  assert(!INSECURE_URI.match(/\s/));
99
+ const uri = new ReadonlyURL(
100
+ resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
101
+ context.host?.href || location.href);
102
+ switch (uri.protocol) {
103
+ case 'tel:': {
104
+ const tel = content.length === 0
105
+ ? INSECURE_URI
106
+ : content[0];
107
+ const pattern = /^(?:tel:)?(?:\+(?!0))?\d+(?:-\d+)*$/i;
108
+ if (content.length <= 1 &&
109
+ typeof tel === 'string' &&
110
+ pattern.test(tel) &&
111
+ pattern.test(INSECURE_URI) &&
112
+ tel.replace(/[^+\d]/g, '') === INSECURE_URI.replace(/[^+\d]/g, '')) {
113
+ break;
114
+ }
115
+ return;
116
+ }
117
+ }
97
118
  const el = elem(
98
119
  INSECURE_URI,
99
- defrag(content),
100
- new ReadonlyURL(
101
- resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
102
- context.host?.href || location.href),
120
+ content,
121
+ uri,
103
122
  context.host?.origin || location.origin);
104
123
  if (el.className === 'invalid') return [[el], rest];
105
124
  return [[define(el, attributes('link', [], optspec, params))], rest];
106
125
  }
107
126
 
108
- export function resolve(uri: string, host: URL | Location, source: URL | Location): string {
109
- assert(uri);
110
- assert(uri === uri.trim());
111
- switch (true) {
112
- case uri.slice(0, 2) === '^/':
113
- const last = host.pathname.slice(host.pathname.lastIndexOf('/') + 1);
114
- return last.includes('.') // isFile
115
- && /^[0-9]*[A-Za-z][0-9A-Za-z]*$/.test(last.slice(last.lastIndexOf('.') + 1))
116
- ? `${host.pathname.slice(0, -last.length)}${uri.slice(2)}`
117
- : `${host.pathname.replace(/\/?$/, '/')}${uri.slice(2)}`;
118
- case host.origin === source.origin
119
- && host.pathname === source.pathname:
120
- case uri.slice(0, 2) === '//':
121
- return uri;
122
- default:
123
- const target = new ReadonlyURL(uri, source.href);
124
- return target.origin === host.origin
125
- ? target.href.slice(target.origin.length)
126
- : target.href;
127
- }
128
- }
129
-
130
127
  function elem(
131
128
  INSECURE_URI: string,
132
129
  content: readonly (string | HTMLElement)[],
@@ -147,7 +144,7 @@ function elem(
147
144
  }
148
145
  return html('a',
149
146
  {
150
- class: 'link',
147
+ class: content.length === 0 ? 'url' : 'link',
151
148
  href: uri.source,
152
149
  target: undefined
153
150
  || uri.origin !== origin
@@ -159,21 +156,15 @@ function elem(
159
156
  ? decode(INSECURE_URI)
160
157
  : content);
161
158
  case 'tel:':
162
- if (content.length === 0) {
163
- content = [INSECURE_URI];
164
- }
165
- const pattern = /^(?:tel:)?(?:\+(?!0))?\d+(?:-\d+)*$/i;
166
- switch (true) {
167
- case content.length === 1
168
- && typeof content[0] === 'string'
169
- && pattern.test(INSECURE_URI)
170
- && pattern.test(content[0])
171
- && INSECURE_URI.replace(/[^+\d]/g, '') === content[0].replace(/[^+\d]/g, ''):
172
- return html('a', { class: 'tel', href: uri.source }, content);
173
- }
174
- type = 'content';
175
- message = 'Invalid phone number';
176
- break;
159
+ assert(content.length <= 1);
160
+ return html('a',
161
+ {
162
+ class: 'tel',
163
+ href: uri.source,
164
+ },
165
+ content.length === 0
166
+ ? [INSECURE_URI]
167
+ : content);
177
168
  }
178
169
  return html('a',
179
170
  {
@@ -187,10 +178,37 @@ function elem(
187
178
  : content);
188
179
  }
189
180
 
181
+ export function resolve(uri: string, host: URL | Location, source: URL | Location): string {
182
+ assert(uri);
183
+ assert(uri === uri.trim());
184
+ switch (true) {
185
+ case uri.slice(0, 2) === '^/':
186
+ const last = host.pathname.slice(host.pathname.lastIndexOf('/') + 1);
187
+ return last.includes('.') // isFile
188
+ && /^[0-9]*[a-z][0-9a-z]*$/i.test(last.slice(last.lastIndexOf('.') + 1))
189
+ ? `${host.pathname.slice(0, -last.length)}${uri.slice(2)}`
190
+ : `${host.pathname.replace(/\/?$/, '/')}${uri.slice(2)}`;
191
+ case host.origin === source.origin
192
+ && host.pathname === source.pathname:
193
+ case uri.slice(0, 2) === '//':
194
+ return uri;
195
+ default:
196
+ const target = new ReadonlyURL(uri, source.href);
197
+ return target.origin === host.origin
198
+ ? target.href.slice(target.origin.length)
199
+ : target.href;
200
+ }
201
+ }
202
+
190
203
  function decode(uri: string): string {
191
204
  if (!uri.includes('%')) return uri;
205
+ const origin = uri.match(/^[a-z](?:[-.](?=\w)|[0-9a-z])*:\/\/[^/?#]*/i)?.[0] ?? '';
192
206
  try {
193
- uri = decodeURI(uri);
207
+ let path = decodeURI(uri.slice(origin.length));
208
+ if (!origin && /^[a-z](?:[-.](?=\w)|[0-9a-z])*:\/\/[^/?#]/i.test(path)) {
209
+ path = uri.slice(origin.length);
210
+ }
211
+ uri = origin + path;
194
212
  }
195
213
  finally {
196
214
  return uri.replace(/\s+/g, encodeURI);
@@ -46,7 +46,7 @@ describe('Unit: parser/inline/reference', () => {
46
46
  assert.deepStrictEqual(inspect(parser('[[`a`]]')), [['<sup class="reference"><span><code data-src="`a`">a</code></span></sup>'], '']);
47
47
  assert.deepStrictEqual(inspect(parser('[[@a]]')), [['<sup class="reference"><span><a class="account" href="/@a">@a</a></span></sup>'], '']);
48
48
  assert.deepStrictEqual(inspect(parser('[[http://host]]')), [['<sup class="reference"><span><a class="url" href="http://host" target="_blank">http://host</a></span></sup>'], '']);
49
- assert.deepStrictEqual(inspect(parser('[[![]{a}]]')), [['<sup class="reference"><span>!<a class="link" href="a">a</a></span></sup>'], '']);
49
+ assert.deepStrictEqual(inspect(parser('[[![]{a}]]')), [['<sup class="reference"><span>!<a class="url" href="a">a</a></span></sup>'], '']);
50
50
  assert.deepStrictEqual(inspect(parser('[[[a]]]')), [['<sup class="reference"><span>[a]</span></sup>'], '']);
51
51
  assert.deepStrictEqual(inspect(parser('[[[[a]]]]')), [['<sup class="reference"><span>[[a]]</span></sup>'], '']);
52
52
  assert.deepStrictEqual(inspect(parser('[[((a))]]')), [['<sup class="reference"><span><span class="paren">((a))</span></span></sup>'], '']);
@@ -48,7 +48,7 @@ export const ruby: RubyParser = lazy(() => validate('[', syntax(Syntax.none, 2,
48
48
  }
49
49
  }))));
50
50
 
51
- const text: RubyParser.TextParser = creation(({ source, context }) => {
51
+ const text: RubyParser.TextParser = creation(1, false, ({ source, context }) => {
52
52
  const acc = [''];
53
53
  while (source !== '') {
54
54
  assert(source[0] !== '\n');
@@ -117,27 +117,27 @@ describe('Unit: parser/inline', () => {
117
117
  assert.deepStrictEqual(inspect(parser('$$-1')), [['$', '<a class="label" data-label="$-1">$-1</a>'], '']);
118
118
  assert.deepStrictEqual(inspect(parser('[[#a]]')), [['<sup class="reference"><span><a class="hashtag" href="/hashtags/a">#a</a></span></sup>'], '']);
119
119
  assert.deepStrictEqual(inspect(parser('[[$-1]]')), [['<sup class="reference"><span><a class="label" data-label="$-1">$-1</a></span></sup>'], '']);
120
- assert.deepStrictEqual(inspect(parser('[[#-1]]{b}')), [['<sup class="reference"><span>#-1</span></sup>', '<a class="link" href="b">b</a>'], '']);
120
+ assert.deepStrictEqual(inspect(parser('[[#-1]]{b}')), [['<sup class="reference"><span>#-1</span></sup>', '<a class="url" href="b">b</a>'], '']);
121
121
  assert.deepStrictEqual(inspect(parser('[[#-1]](b)')), [['<sup class="reference"><span>#-1</span></sup>', '(', 'b', ')'], '']);
122
122
  assert.deepStrictEqual(inspect(parser('[[#-1]a]{b}')), [['<a class="link" href="b">[#-1]a</a>'], '']);
123
123
  assert.deepStrictEqual(inspect(parser('[[#-1]a](b)')), [['[', '<a class="index" href="#index:-1">-1</a>', 'a', ']', '(', 'b', ')'], '']);
124
- assert.deepStrictEqual(inspect(parser('[#a]{b}')), [['<a class="index" href="#index:a">a</a>', '<a class="link" href="b">b</a>'], '']);
124
+ assert.deepStrictEqual(inspect(parser('[#a]{b}')), [['<a class="index" href="#index:a">a</a>', '<a class="url" href="b">b</a>'], '']);
125
125
  assert.deepStrictEqual(inspect(parser('[@a]{b}')), [['<a class="link" href="b">@a</a>'], '']);
126
- assert.deepStrictEqual(inspect(parser('[http://host]{http://evil}')), [['[', '<a class="url" href="http://host" target="_blank">http://host</a>', ']', '<a class="link" href="http://evil" target="_blank">http://evil</a>'], '']);
127
- assert.deepStrictEqual(inspect(parser('[http://host]{http://host}')), [['[', '<a class="url" href="http://host" target="_blank">http://host</a>', ']', '<a class="link" href="http://host" target="_blank">http://host</a>'], '']);
126
+ assert.deepStrictEqual(inspect(parser('[http://host]{http://evil}')), [['[', '<a class="url" href="http://host" target="_blank">http://host</a>', ']', '<a class="url" href="http://evil" target="_blank">http://evil</a>'], '']);
127
+ assert.deepStrictEqual(inspect(parser('[http://host]{http://host}')), [['[', '<a class="url" href="http://host" target="_blank">http://host</a>', ']', '<a class="url" href="http://host" target="_blank">http://host</a>'], '']);
128
128
  assert.deepStrictEqual(inspect(parser('[]{{a}}')), [['[', ']', '<span class="template">{{a}}</span>'], '']);
129
129
  assert.deepStrictEqual(inspect(parser('![]{{a}}')), [['!', '[', ']', '<span class="template">{{a}}</span>'], '']);
130
- assert.deepStrictEqual(inspect(parser('[\n]{a}')), [['[', '<br>', ']', '<a class="link" href="a">a</a>'], '']);
131
- assert.deepStrictEqual(inspect(parser('[\\\n]{a}')), [['[', '<span class="linebreak"> </span>', ']', '<a class="link" href="a">a</a>'], '']);
130
+ assert.deepStrictEqual(inspect(parser('[\n]{a}')), [['[', '<br>', ']', '<a class="url" href="a">a</a>'], '']);
131
+ assert.deepStrictEqual(inspect(parser('[\\\n]{a}')), [['[', '<span class="linebreak"> </span>', ']', '<a class="url" href="a">a</a>'], '']);
132
132
  assert.deepStrictEqual(inspect(parser('{}')), [['{', '}'], '']);
133
- assert.deepStrictEqual(inspect(parser('{a}')), [['<a class="link" href="a">a</a>'], '']);
133
+ assert.deepStrictEqual(inspect(parser('{a}')), [['<a class="url" href="a">a</a>'], '']);
134
134
  assert.deepStrictEqual(inspect(parser('{{a}}')), [['<span class="template">{{a}}</span>'], '']);
135
135
  assert.deepStrictEqual(inspect(parser('!{}')), [['!', '{', '}'], '']);
136
136
  assert.deepStrictEqual(inspect(parser('!{a}')), [['<a href="a" target="_blank"><img class="media" data-src="a" alt=""></a>'], '']);
137
137
  assert.deepStrictEqual(inspect(parser('!{{a}}')), [['!', '<span class="template">{{a}}</span>'], '']);
138
138
  assert.deepStrictEqual(inspect(parser('!{{{a}}}')), [['!', '<span class="template">{{{a}}}</span>'], '']);
139
139
  assert.deepStrictEqual(inspect(parser('!!{a}')), [['!', '<a href="a" target="_blank"><img class="media" data-src="a" alt=""></a>'], '']);
140
- assert.deepStrictEqual(inspect(parser('${a}')), [['$', '<a class="link" href="a">a</a>'], '']);
140
+ assert.deepStrictEqual(inspect(parser('${a}')), [['$', '<a class="url" href="a">a</a>'], '']);
141
141
  assert.deepStrictEqual(inspect(parser('${{a}}')), [['$', '<span class="template">{{a}}</span>'], '']);
142
142
  assert.deepStrictEqual(inspect(parser('${{{a}}}')), [['$', '<span class="template">{{{a}}}</span>'], '']);
143
143
  assert.deepStrictEqual(inspect(parser('Di$ney Micro$oft')), [['Di', '$', 'ney', ' ', 'Micro', '$', 'oft'], '']);
@@ -152,14 +152,14 @@ describe('Unit: parser/inline', () => {
152
152
  assert.deepStrictEqual(inspect(parser('[[[[a]]')), [['', '[', '', '[', '<sup class="reference"><span>a</span></sup>'], '']);
153
153
  assert.deepStrictEqual(inspect(parser('[[[[a]]]]')), [['<sup class="reference"><span>[[a]]</span></sup>'], '']);
154
154
  assert.deepStrictEqual(inspect(parser('[[[$-1]]]')), [['<sup class="reference"><span><a class="label" data-label="$-1">$-1</a></span></sup>'], '']);
155
- assert.deepStrictEqual(inspect(parser('[[[]{a}]]')), [['<sup class="reference"><span><a class="link" href="a">a</a></span></sup>'], '']);
155
+ assert.deepStrictEqual(inspect(parser('[[[]{a}]]')), [['<sup class="reference"><span><a class="url" href="a">a</a></span></sup>'], '']);
156
156
  assert.deepStrictEqual(inspect(parser('[[[a]{b}]]')), [['<sup class="reference"><span><a class="link" href="b">a</a></span></sup>'], '']);
157
157
  assert.deepStrictEqual(inspect(parser('[(([a]{#}))]{#}')), [['<a class="link" href="#"><span class="paren">(<span class="paren">([a]{#})</span>)</span></a>'], '']);
158
158
  assert.deepStrictEqual(inspect(parser('[[<bdi>]]')), [['<sup class="reference"><span><span class="invalid">&lt;bdi&gt;</span></span></sup>'], '']);
159
159
  assert.deepStrictEqual(inspect(parser('[[${]]}$')), [['', '[', '', '[', '<span class="math" translate="no" data-src="${]]}$">${]]}$</span>'], '']);
160
160
  assert.deepStrictEqual(inspect(parser('"[[""]]')), [['"', '<sup class="reference"><span>""</span></sup>'], '']);
161
161
  assert.deepStrictEqual(inspect(parser('[[a](b)]{c}')), [['<a class="link" href="c"><ruby>a<rp>(</rp><rt>b</rt><rp>)</rp></ruby></a>'], '']);
162
- assert.deepStrictEqual(inspect(parser('[[[[[[[{a}')), [['', '[', '', '[', '', '[', '', '[', '', '[', '', '[', '', '[', '<a class="link" href="a">a</a>'], '']);
162
+ assert.deepStrictEqual(inspect(parser('[[[[[[[{a}')), [['', '[', '', '[', '', '[', '', '[', '', '[', '', '[', '', '[', '<a class="url" href="a">a</a>'], '']);
163
163
  assert.deepStrictEqual(inspect(parser('<http://host>')), [['<', '<a class="url" href="http://host" target="_blank">http://host</a>', '>'], '']);
164
164
  assert.deepStrictEqual(inspect(parser('[~http://host')), [['', '[', '~', '<a class="url" href="http://host" target="_blank">http://host</a>'], '']);
165
165
  assert.deepStrictEqual(inspect(parser('[~a@b')), [['', '[', '~', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
@@ -167,7 +167,7 @@ describe('Unit: parser/inline', () => {
167
167
  assert.deepStrictEqual(inspect(parser('[^http://host')), [['[^', '<a class="url" href="http://host" target="_blank">http://host</a>'], '']);
168
168
  assert.deepStrictEqual(inspect(parser('[^a@b')), [['[^', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
169
169
  assert.deepStrictEqual(inspect(parser('[#a*b\nc*]')), [['[', '<a class="hashtag" href="/hashtags/a">#a</a>', '<em>b<br>c</em>', ']'], '']);
170
- assert.deepStrictEqual(inspect(parser('[*a\nb*]{/}')), [['[', '<em>a<br>b</em>', ']', '<a class="link" href="/">/</a>'], '']);
170
+ assert.deepStrictEqual(inspect(parser('[*a\nb*]{/}')), [['[', '<em>a<br>b</em>', ']', '<a class="url" href="/">/</a>'], '']);
171
171
  assert.deepStrictEqual(inspect(parser('[*a\nb]*')), [['[', '*', 'a', '<br>', 'b', ']', '*'], '']);
172
172
  assert.deepStrictEqual(inspect(parser('[*[a\nb*]')), [['', '[', '*', '[', 'a', '<br>', 'b', '*', ']'], '']);
173
173
  assert.deepStrictEqual(inspect(parser('[[*a\nb*]]')), [['[', '[', '<em>a<br>b</em>', ']', ']'], '']);
@@ -4,7 +4,7 @@ import { nonWhitespace } from './text';
4
4
 
5
5
  const delimiter = /[\s\x00-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]/;
6
6
 
7
- export const escsource: EscapableSourceParser = creation(({ source }) => {
7
+ export const escsource: EscapableSourceParser = creation(1, false, ({ source }) => {
8
8
  if (source === '') return;
9
9
  const i = source.search(delimiter);
10
10
  switch (i) {
@@ -7,13 +7,13 @@ export function str(pattern: string | RegExp): StrParser;
7
7
  export function str(pattern: string | RegExp): Parser<string, Context<StrParser>, []> {
8
8
  assert(pattern);
9
9
  return typeof pattern === 'string'
10
- ? creation(({ source }) => {
10
+ ? creation(1, false, ({ source }) => {
11
11
  if (source === '') return;
12
12
  return source.slice(0, pattern.length) === pattern
13
13
  ? [[pattern], source.slice(pattern.length)]
14
14
  : undefined;
15
15
  })
16
- : creation(({ source }) => {
16
+ : creation(1, false, ({ source }) => {
17
17
  if (source === '') return;
18
18
  const m = source.match(pattern);
19
19
  return m && m[0].length > 0
@@ -26,13 +26,13 @@ export function stropt(pattern: string | RegExp): StrParser;
26
26
  export function stropt(pattern: string | RegExp): Parser<string, Context<StrParser>, []> {
27
27
  assert(pattern);
28
28
  return typeof pattern === 'string'
29
- ? creation(({ source }) => {
29
+ ? creation(1, false, ({ source }) => {
30
30
  if (source === '') return;
31
31
  return source.slice(0, pattern.length) === pattern
32
32
  ? [[pattern], source.slice(pattern.length)]
33
33
  : undefined;
34
34
  })
35
- : creation(({ source }) => {
35
+ : creation(1, false, ({ source }) => {
36
36
  if (source === '') return;
37
37
  const m = source.match(pattern);
38
38
  return m
@@ -1,8 +1,7 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { TextParser, TxtParser, LinebreakParser } from '../source';
3
- import { union, syntax, focus } from '../../combinator';
3
+ import { union, creation, focus } from '../../combinator';
4
4
  import { str } from './str';
5
- import { Syntax, State } from '../context';
6
5
  import { html } from 'typed-dom/dom';
7
6
 
8
7
  export const delimiter = /[\s\x00-\x7F]|\S[#>]|[()、。!?][^\S\n]*(?=\\\n)/;
@@ -10,7 +9,7 @@ export const nonWhitespace = /[\S\n]|$/;
10
9
  export const nonAlphanumeric = /[^0-9A-Za-z]|\S[#>]|$/;
11
10
  const repeat = str(/^(.)\1*/);
12
11
 
13
- export const text: TextParser = syntax(Syntax.none, 1, 1, State.none, ({ source, context }) => {
12
+ export const text: TextParser = creation(1, false, ({ source, context }) => {
14
13
  if (source === '') return;
15
14
  const i = source.search(delimiter);
16
15
  switch (i) {
@@ -2,7 +2,7 @@ import { UnescapableSourceParser } from '../source';
2
2
  import { creation } from '../../combinator';
3
3
  import { delimiter, nonWhitespace, nonAlphanumeric, isAlphanumeric } from './text';
4
4
 
5
- export const unescsource: UnescapableSourceParser = creation(({ source }) => {
5
+ export const unescsource: UnescapableSourceParser = creation(1, false, ({ source }) => {
6
6
  assert(source[0] !== '\x1B');
7
7
  if (source === '') return;
8
8
  const i = source.search(delimiter);
@@ -37,7 +37,13 @@ export function twitter(source: HTMLImageElement, url: URL): HTMLElement | undef
37
37
  },
38
38
  error({ status, statusText }) {
39
39
  assert(Number.isSafeInteger(status));
40
- define(el, [parse(`*{ ${source.getAttribute('data-src')} }*\n\n\`\`\`\n${status}\n${statusText}\n\`\`\``)]);
40
+ define(el, [
41
+ define(parse(`{ ${source.getAttribute('data-src')} }`).querySelector('a')!, {
42
+ class: null,
43
+ target: '_blank',
44
+ }),
45
+ h('pre', `${status}\n${statusText}`),
46
+ ]);
41
47
  },
42
48
  });
43
49
  return el;