@theia/preview 1.45.0 → 1.46.0-next.72

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 (42) hide show
  1. package/README.md +45 -45
  2. package/lib/browser/index.d.ts +3 -3
  3. package/lib/browser/index.js +29 -29
  4. package/lib/browser/markdown/index.d.ts +1 -1
  5. package/lib/browser/markdown/index.js +28 -28
  6. package/lib/browser/markdown/markdown-preview-handler.d.ts +28 -28
  7. package/lib/browser/markdown/markdown-preview-handler.js +301 -301
  8. package/lib/browser/markdown/markdown-preview-handler.spec.d.ts +1 -1
  9. package/lib/browser/markdown/markdown-preview-handler.spec.js +193 -193
  10. package/lib/browser/preview-contribution.d.ts +50 -50
  11. package/lib/browser/preview-contribution.js +262 -262
  12. package/lib/browser/preview-frontend-module.d.ts +5 -5
  13. package/lib/browser/preview-frontend-module.js +52 -52
  14. package/lib/browser/preview-handler.d.ts +104 -104
  15. package/lib/browser/preview-handler.js +76 -76
  16. package/lib/browser/preview-link-normalizer.d.ts +7 -7
  17. package/lib/browser/preview-link-normalizer.js +54 -54
  18. package/lib/browser/preview-preferences.d.ts +11 -11
  19. package/lib/browser/preview-preferences.js +46 -46
  20. package/lib/browser/preview-uri.d.ts +8 -8
  21. package/lib/browser/preview-uri.js +47 -47
  22. package/lib/browser/preview-widget.d.ts +54 -54
  23. package/lib/browser/preview-widget.js +261 -261
  24. package/lib/package.spec.js +25 -25
  25. package/package.json +7 -7
  26. package/src/browser/index.ts +19 -19
  27. package/src/browser/markdown/index.ts +17 -17
  28. package/src/browser/markdown/markdown-preview-handler.spec.ts +228 -228
  29. package/src/browser/markdown/markdown-preview-handler.ts +309 -309
  30. package/src/browser/markdown/style/index.css +18 -18
  31. package/src/browser/markdown/style/markdown.css +203 -203
  32. package/src/browser/markdown/style/tomorrow.css +105 -105
  33. package/src/browser/preview-contribution.ts +276 -276
  34. package/src/browser/preview-frontend-module.ts +57 -57
  35. package/src/browser/preview-handler.ts +141 -141
  36. package/src/browser/preview-link-normalizer.ts +40 -40
  37. package/src/browser/preview-preferences.ts +58 -58
  38. package/src/browser/preview-uri.ts +43 -43
  39. package/src/browser/preview-widget.ts +277 -277
  40. package/src/browser/style/index.css +17 -17
  41. package/src/browser/style/preview-widget.css +29 -29
  42. package/src/package.spec.ts +29 -29
@@ -1,309 +1,309 @@
1
- // *****************************************************************************
2
- // Copyright (C) 2018 TypeFox and others.
3
- //
4
- // This program and the accompanying materials are made available under the
5
- // terms of the Eclipse Public License v. 2.0 which is available at
6
- // http://www.eclipse.org/legal/epl-2.0.
7
- //
8
- // This Source Code may also be made available under the following Secondary
9
- // Licenses when the conditions for such availability set forth in the Eclipse
10
- // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- // with the GNU Classpath Exception which is available at
12
- // https://www.gnu.org/software/classpath/license.html.
13
- //
14
- // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
- // *****************************************************************************
16
-
17
- import { injectable, inject } from '@theia/core/shared/inversify';
18
- import URI from '@theia/core/lib/common/uri';
19
- import { OpenerService } from '@theia/core/lib/browser';
20
- import { isOSX } from '@theia/core/lib/common';
21
- import { Path } from '@theia/core/lib/common/path';
22
-
23
- import * as hljs from 'highlight.js';
24
- import * as markdownit from '@theia/core/shared/markdown-it';
25
- import * as anchor from 'markdown-it-anchor';
26
- import * as DOMPurify from '@theia/core/shared/dompurify';
27
- import { PreviewUri } from '../preview-uri';
28
- import { PreviewHandler, RenderContentParams } from '../preview-handler';
29
- import { PreviewOpenerOptions } from '../preview-contribution';
30
- import { PreviewLinkNormalizer } from '../preview-link-normalizer';
31
-
32
- @injectable()
33
- export class MarkdownPreviewHandler implements PreviewHandler {
34
-
35
- readonly iconClass: string = 'markdown-icon file-icon';
36
- readonly contentClass: string = 'markdown-preview';
37
-
38
- @inject(OpenerService)
39
- protected readonly openerService: OpenerService;
40
-
41
- @inject(PreviewLinkNormalizer)
42
- protected readonly linkNormalizer: PreviewLinkNormalizer;
43
-
44
- canHandle(uri: URI): number {
45
- return uri.scheme === 'file'
46
- && (
47
- uri.path.ext.toLowerCase() === '.md' ||
48
- uri.path.ext.toLowerCase() === '.markdown'
49
- ) ? 500 : 0;
50
- }
51
-
52
- renderContent(params: RenderContentParams): HTMLElement {
53
- const content = params.content;
54
- const renderedContent = this.getEngine().render(content, params);
55
- const contentElement = document.createElement('div');
56
- contentElement.classList.add(this.contentClass);
57
- contentElement.innerHTML = DOMPurify.sanitize(renderedContent);
58
- this.addLinkClickedListener(contentElement, params);
59
- return contentElement;
60
- }
61
-
62
- protected addLinkClickedListener(contentElement: HTMLElement, params: RenderContentParams): void {
63
- contentElement.addEventListener('click', (event: MouseEvent) => {
64
- const candidate = (event.target || event.srcElement) as HTMLElement;
65
- const link = this.findLink(candidate, contentElement);
66
- if (link) {
67
- event.preventDefault();
68
- if (link.startsWith('#')) {
69
- this.revealFragment(contentElement, link);
70
- } else {
71
- const preview = !(isOSX ? event.metaKey : event.ctrlKey);
72
- const uri = this.resolveUri(link, params.originUri, preview);
73
- this.openLink(uri, params.originUri);
74
- }
75
- }
76
- });
77
- }
78
-
79
- protected findLink(element: HTMLElement, container: HTMLElement): string | undefined {
80
- let candidate = element;
81
- while (candidate.tagName !== 'A') {
82
- if (candidate === container) {
83
- return;
84
- }
85
- candidate = candidate.parentElement!;
86
- if (!candidate) {
87
- return;
88
- }
89
- }
90
- return candidate.getAttribute('href') || undefined;
91
- }
92
-
93
- protected async openLink(uri: URI, originUri: URI): Promise<void> {
94
- const opener = await this.openerService.getOpener(uri);
95
- opener.open(uri, <PreviewOpenerOptions>{ originUri });
96
- }
97
-
98
- protected resolveUri(link: string, uri: URI, preview: boolean): URI {
99
- const linkURI = new URI(link);
100
- // URIs are always absolute, check link as a path whether it is relative
101
- if (!new Path(link).isAbsolute && linkURI.scheme === uri.scheme &&
102
- (!linkURI.authority || linkURI.authority === uri.authority)) {
103
- // get a relative path from URI by trimming leading `/`
104
- const relativePath = linkURI.path.toString().substring(1);
105
- const resolvedUri = uri.parent.resolve(relativePath).withFragment(linkURI.fragment).withQuery(linkURI.query);
106
- return preview ? PreviewUri.encode(resolvedUri) : resolvedUri;
107
- }
108
- return linkURI;
109
- }
110
-
111
- protected revealFragment(contentElement: HTMLElement, fragment: string): void {
112
- const elementToReveal = this.findElementForFragment(contentElement, fragment);
113
- if (!elementToReveal) {
114
- return;
115
- }
116
- elementToReveal.scrollIntoView();
117
- }
118
-
119
- findElementForFragment(content: HTMLElement, link: string): HTMLElement | undefined {
120
- const fragment = link.startsWith('#') ? link.substring(1) : link;
121
- const filter: NodeFilter = {
122
- acceptNode: (node: Node) => {
123
- if (node instanceof HTMLHeadingElement) {
124
- if (node.tagName.toLowerCase().startsWith('h') && node.id === fragment) {
125
- return NodeFilter.FILTER_ACCEPT;
126
- }
127
- return NodeFilter.FILTER_SKIP;
128
- }
129
- return NodeFilter.FILTER_SKIP;
130
- }
131
- };
132
- const treeWalker = document.createTreeWalker(content, NodeFilter.SHOW_ELEMENT, filter);
133
- if (treeWalker.nextNode()) {
134
- const element = treeWalker.currentNode as HTMLElement;
135
- return element;
136
- }
137
- return undefined;
138
- }
139
-
140
- findElementForSourceLine(content: HTMLElement, sourceLine: number): HTMLElement | undefined {
141
- const markedElements = content.getElementsByClassName('line');
142
- let matchedElement: HTMLElement | undefined;
143
- for (let i = 0; i < markedElements.length; i++) {
144
- const element = markedElements[i];
145
- const line = Number.parseInt(element.getAttribute('data-line') || '0');
146
- if (line > sourceLine) {
147
- break;
148
- }
149
- matchedElement = element as HTMLElement;
150
- }
151
- return matchedElement;
152
- }
153
-
154
- getSourceLineForOffset(content: HTMLElement, offset: number): number | undefined {
155
- const lineElements = this.getLineElementsAtOffset(content, offset);
156
- if (lineElements.length < 1) {
157
- return undefined;
158
- }
159
- const firstLineNumber = this.getLineNumberFromAttribute(lineElements[0]);
160
- if (firstLineNumber === undefined) {
161
- return undefined;
162
- }
163
- if (lineElements.length === 1) {
164
- return firstLineNumber;
165
- }
166
- const secondLineNumber = this.getLineNumberFromAttribute(lineElements[1]);
167
- if (secondLineNumber === undefined) {
168
- return firstLineNumber;
169
- }
170
- const y1 = lineElements[0].offsetTop;
171
- const y2 = lineElements[1].offsetTop;
172
- const dY = (offset - y1) / (y2 - y1);
173
- const dL = (secondLineNumber - firstLineNumber) * dY;
174
- const line = firstLineNumber + Math.floor(dL);
175
- return line;
176
- }
177
-
178
- /**
179
- * returns two significant line elements for the given offset.
180
- */
181
- protected getLineElementsAtOffset(content: HTMLElement, offset: number): HTMLElement[] {
182
- let skipNext = false;
183
- const filter: NodeFilter = {
184
- acceptNode: (node: Node) => {
185
- if (node instanceof HTMLElement) {
186
- if (node.classList.contains('line')) {
187
- if (skipNext) {
188
- return NodeFilter.FILTER_SKIP;
189
- }
190
- if (node.offsetTop > offset) {
191
- skipNext = true;
192
- }
193
- return NodeFilter.FILTER_ACCEPT;
194
- }
195
- return NodeFilter.FILTER_SKIP;
196
- }
197
- return NodeFilter.FILTER_REJECT;
198
- }
199
- };
200
- const treeWalker = document.createTreeWalker(content, NodeFilter.SHOW_ELEMENT, filter);
201
- const lineElements: HTMLElement[] = [];
202
- while (treeWalker.nextNode()) {
203
- const element = treeWalker.currentNode as HTMLElement;
204
- lineElements.push(element);
205
- }
206
- return lineElements.slice(-2);
207
- }
208
-
209
- protected getLineNumberFromAttribute(element: HTMLElement): number | undefined {
210
- const attribute = element.getAttribute('data-line');
211
- return attribute ? Number.parseInt(attribute) : undefined;
212
- }
213
-
214
- protected engine: markdownit | undefined;
215
- protected getEngine(): markdownit {
216
- if (!this.engine) {
217
- const engine: markdownit = this.engine = markdownit({
218
- html: true,
219
- linkify: true,
220
- highlight: (str, lang) => {
221
- if (lang && hljs.getLanguage(lang)) {
222
- try {
223
- return '<pre class="hljs"><code><div>' + hljs.highlight(lang, str, true).value + '</div></code></pre>';
224
- } catch { }
225
- }
226
- return '<pre class="hljs"><code><div>' + engine.utils.escapeHtml(str) + '</div></code></pre>';
227
- }
228
- });
229
- const renderers = ['heading_open', 'paragraph_open', 'list_item_open', 'blockquote_open', 'code_block', 'image', 'fence'];
230
- for (const renderer of renderers) {
231
- const originalRenderer = engine.renderer.rules[renderer];
232
- engine.renderer.rules[renderer] = (tokens, index, options, env, self) => {
233
- const token = tokens[index];
234
- if (token.map) {
235
- const line = token.map[0];
236
- token.attrJoin('class', 'line');
237
- token.attrSet('data-line', line.toString());
238
- }
239
- return (originalRenderer)
240
- // tslint:disable-next-line:no-void-expression
241
- ? originalRenderer(tokens, index, options, env, self)
242
- : self.renderToken(tokens, index, options);
243
- };
244
- }
245
- const originalImageRenderer = engine.renderer.rules.image;
246
- if (originalImageRenderer) {
247
- engine.renderer.rules.image = (tokens, index, options, env, self) => {
248
- if (RenderContentParams.is(env)) {
249
- const documentUri = env.originUri;
250
- const token = tokens[index];
251
- if (token.attrs) {
252
- const srcAttr = token.attrs.find(a => a[0] === 'src');
253
- if (srcAttr) {
254
- const href = srcAttr[1];
255
- srcAttr[1] = this.linkNormalizer.normalizeLink(documentUri, href);
256
- }
257
- }
258
- }
259
- return originalImageRenderer(tokens, index, options, env, self);
260
- };
261
- }
262
-
263
- const domParser = new DOMParser();
264
-
265
- const parseDOM = (html: string) =>
266
- domParser.parseFromString(html, 'text/html').getElementsByTagName('body')[0] as HTMLElement;
267
-
268
- const modifyDOM = (body: HTMLElement, tag: string, procedure: (element: Element) => void) => {
269
- const elements = body.getElementsByTagName(tag);
270
- for (let i = 0; i < elements.length; i++) {
271
- const element = elements.item(i);
272
- if (element) {
273
- procedure(element);
274
- }
275
- }
276
- };
277
-
278
- const normalizeAllImgSrcInHTML = (html: string, normalizeLink: (link: string) => string) => {
279
- const body = parseDOM(html);
280
- modifyDOM(body, 'img', img => {
281
- const src = img.getAttributeNode('src');
282
- if (src) {
283
- src.nodeValue = normalizeLink(src.nodeValue || '');
284
- }
285
- });
286
- return body.innerHTML;
287
- };
288
-
289
- for (const name of ['html_block', 'html_inline']) {
290
- const originalRenderer = engine.renderer.rules[name];
291
- if (originalRenderer) {
292
- engine.renderer.rules[name] = (tokens, index, options, env, self) => {
293
- const currentToken = tokens[index];
294
- const content = currentToken.content;
295
- if (content.includes('<img') && RenderContentParams.is(env)) {
296
- const documentUri = env.originUri;
297
- currentToken.content = normalizeAllImgSrcInHTML(content, link => this.linkNormalizer.normalizeLink(documentUri, link));
298
- }
299
- return originalRenderer(tokens, index, options, env, self);
300
- };
301
- }
302
- }
303
-
304
- anchor(engine, {});
305
- }
306
- return this.engine;
307
- }
308
-
309
- }
1
+ // *****************************************************************************
2
+ // Copyright (C) 2018 TypeFox and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { injectable, inject } from '@theia/core/shared/inversify';
18
+ import URI from '@theia/core/lib/common/uri';
19
+ import { OpenerService } from '@theia/core/lib/browser';
20
+ import { isOSX } from '@theia/core/lib/common';
21
+ import { Path } from '@theia/core/lib/common/path';
22
+
23
+ import * as hljs from 'highlight.js';
24
+ import * as markdownit from '@theia/core/shared/markdown-it';
25
+ import * as anchor from 'markdown-it-anchor';
26
+ import * as DOMPurify from '@theia/core/shared/dompurify';
27
+ import { PreviewUri } from '../preview-uri';
28
+ import { PreviewHandler, RenderContentParams } from '../preview-handler';
29
+ import { PreviewOpenerOptions } from '../preview-contribution';
30
+ import { PreviewLinkNormalizer } from '../preview-link-normalizer';
31
+
32
+ @injectable()
33
+ export class MarkdownPreviewHandler implements PreviewHandler {
34
+
35
+ readonly iconClass: string = 'markdown-icon file-icon';
36
+ readonly contentClass: string = 'markdown-preview';
37
+
38
+ @inject(OpenerService)
39
+ protected readonly openerService: OpenerService;
40
+
41
+ @inject(PreviewLinkNormalizer)
42
+ protected readonly linkNormalizer: PreviewLinkNormalizer;
43
+
44
+ canHandle(uri: URI): number {
45
+ return uri.scheme === 'file'
46
+ && (
47
+ uri.path.ext.toLowerCase() === '.md' ||
48
+ uri.path.ext.toLowerCase() === '.markdown'
49
+ ) ? 500 : 0;
50
+ }
51
+
52
+ renderContent(params: RenderContentParams): HTMLElement {
53
+ const content = params.content;
54
+ const renderedContent = this.getEngine().render(content, params);
55
+ const contentElement = document.createElement('div');
56
+ contentElement.classList.add(this.contentClass);
57
+ contentElement.innerHTML = DOMPurify.sanitize(renderedContent);
58
+ this.addLinkClickedListener(contentElement, params);
59
+ return contentElement;
60
+ }
61
+
62
+ protected addLinkClickedListener(contentElement: HTMLElement, params: RenderContentParams): void {
63
+ contentElement.addEventListener('click', (event: MouseEvent) => {
64
+ const candidate = (event.target || event.srcElement) as HTMLElement;
65
+ const link = this.findLink(candidate, contentElement);
66
+ if (link) {
67
+ event.preventDefault();
68
+ if (link.startsWith('#')) {
69
+ this.revealFragment(contentElement, link);
70
+ } else {
71
+ const preview = !(isOSX ? event.metaKey : event.ctrlKey);
72
+ const uri = this.resolveUri(link, params.originUri, preview);
73
+ this.openLink(uri, params.originUri);
74
+ }
75
+ }
76
+ });
77
+ }
78
+
79
+ protected findLink(element: HTMLElement, container: HTMLElement): string | undefined {
80
+ let candidate = element;
81
+ while (candidate.tagName !== 'A') {
82
+ if (candidate === container) {
83
+ return;
84
+ }
85
+ candidate = candidate.parentElement!;
86
+ if (!candidate) {
87
+ return;
88
+ }
89
+ }
90
+ return candidate.getAttribute('href') || undefined;
91
+ }
92
+
93
+ protected async openLink(uri: URI, originUri: URI): Promise<void> {
94
+ const opener = await this.openerService.getOpener(uri);
95
+ opener.open(uri, <PreviewOpenerOptions>{ originUri });
96
+ }
97
+
98
+ protected resolveUri(link: string, uri: URI, preview: boolean): URI {
99
+ const linkURI = new URI(link);
100
+ // URIs are always absolute, check link as a path whether it is relative
101
+ if (!new Path(link).isAbsolute && linkURI.scheme === uri.scheme &&
102
+ (!linkURI.authority || linkURI.authority === uri.authority)) {
103
+ // get a relative path from URI by trimming leading `/`
104
+ const relativePath = linkURI.path.toString().substring(1);
105
+ const resolvedUri = uri.parent.resolve(relativePath).withFragment(linkURI.fragment).withQuery(linkURI.query);
106
+ return preview ? PreviewUri.encode(resolvedUri) : resolvedUri;
107
+ }
108
+ return linkURI;
109
+ }
110
+
111
+ protected revealFragment(contentElement: HTMLElement, fragment: string): void {
112
+ const elementToReveal = this.findElementForFragment(contentElement, fragment);
113
+ if (!elementToReveal) {
114
+ return;
115
+ }
116
+ elementToReveal.scrollIntoView();
117
+ }
118
+
119
+ findElementForFragment(content: HTMLElement, link: string): HTMLElement | undefined {
120
+ const fragment = link.startsWith('#') ? link.substring(1) : link;
121
+ const filter: NodeFilter = {
122
+ acceptNode: (node: Node) => {
123
+ if (node instanceof HTMLHeadingElement) {
124
+ if (node.tagName.toLowerCase().startsWith('h') && node.id === fragment) {
125
+ return NodeFilter.FILTER_ACCEPT;
126
+ }
127
+ return NodeFilter.FILTER_SKIP;
128
+ }
129
+ return NodeFilter.FILTER_SKIP;
130
+ }
131
+ };
132
+ const treeWalker = document.createTreeWalker(content, NodeFilter.SHOW_ELEMENT, filter);
133
+ if (treeWalker.nextNode()) {
134
+ const element = treeWalker.currentNode as HTMLElement;
135
+ return element;
136
+ }
137
+ return undefined;
138
+ }
139
+
140
+ findElementForSourceLine(content: HTMLElement, sourceLine: number): HTMLElement | undefined {
141
+ const markedElements = content.getElementsByClassName('line');
142
+ let matchedElement: HTMLElement | undefined;
143
+ for (let i = 0; i < markedElements.length; i++) {
144
+ const element = markedElements[i];
145
+ const line = Number.parseInt(element.getAttribute('data-line') || '0');
146
+ if (line > sourceLine) {
147
+ break;
148
+ }
149
+ matchedElement = element as HTMLElement;
150
+ }
151
+ return matchedElement;
152
+ }
153
+
154
+ getSourceLineForOffset(content: HTMLElement, offset: number): number | undefined {
155
+ const lineElements = this.getLineElementsAtOffset(content, offset);
156
+ if (lineElements.length < 1) {
157
+ return undefined;
158
+ }
159
+ const firstLineNumber = this.getLineNumberFromAttribute(lineElements[0]);
160
+ if (firstLineNumber === undefined) {
161
+ return undefined;
162
+ }
163
+ if (lineElements.length === 1) {
164
+ return firstLineNumber;
165
+ }
166
+ const secondLineNumber = this.getLineNumberFromAttribute(lineElements[1]);
167
+ if (secondLineNumber === undefined) {
168
+ return firstLineNumber;
169
+ }
170
+ const y1 = lineElements[0].offsetTop;
171
+ const y2 = lineElements[1].offsetTop;
172
+ const dY = (offset - y1) / (y2 - y1);
173
+ const dL = (secondLineNumber - firstLineNumber) * dY;
174
+ const line = firstLineNumber + Math.floor(dL);
175
+ return line;
176
+ }
177
+
178
+ /**
179
+ * returns two significant line elements for the given offset.
180
+ */
181
+ protected getLineElementsAtOffset(content: HTMLElement, offset: number): HTMLElement[] {
182
+ let skipNext = false;
183
+ const filter: NodeFilter = {
184
+ acceptNode: (node: Node) => {
185
+ if (node instanceof HTMLElement) {
186
+ if (node.classList.contains('line')) {
187
+ if (skipNext) {
188
+ return NodeFilter.FILTER_SKIP;
189
+ }
190
+ if (node.offsetTop > offset) {
191
+ skipNext = true;
192
+ }
193
+ return NodeFilter.FILTER_ACCEPT;
194
+ }
195
+ return NodeFilter.FILTER_SKIP;
196
+ }
197
+ return NodeFilter.FILTER_REJECT;
198
+ }
199
+ };
200
+ const treeWalker = document.createTreeWalker(content, NodeFilter.SHOW_ELEMENT, filter);
201
+ const lineElements: HTMLElement[] = [];
202
+ while (treeWalker.nextNode()) {
203
+ const element = treeWalker.currentNode as HTMLElement;
204
+ lineElements.push(element);
205
+ }
206
+ return lineElements.slice(-2);
207
+ }
208
+
209
+ protected getLineNumberFromAttribute(element: HTMLElement): number | undefined {
210
+ const attribute = element.getAttribute('data-line');
211
+ return attribute ? Number.parseInt(attribute) : undefined;
212
+ }
213
+
214
+ protected engine: markdownit | undefined;
215
+ protected getEngine(): markdownit {
216
+ if (!this.engine) {
217
+ const engine: markdownit = this.engine = markdownit({
218
+ html: true,
219
+ linkify: true,
220
+ highlight: (str, lang) => {
221
+ if (lang && hljs.getLanguage(lang)) {
222
+ try {
223
+ return '<pre class="hljs"><code><div>' + hljs.highlight(lang, str, true).value + '</div></code></pre>';
224
+ } catch { }
225
+ }
226
+ return '<pre class="hljs"><code><div>' + engine.utils.escapeHtml(str) + '</div></code></pre>';
227
+ }
228
+ });
229
+ const renderers = ['heading_open', 'paragraph_open', 'list_item_open', 'blockquote_open', 'code_block', 'image', 'fence'];
230
+ for (const renderer of renderers) {
231
+ const originalRenderer = engine.renderer.rules[renderer];
232
+ engine.renderer.rules[renderer] = (tokens, index, options, env, self) => {
233
+ const token = tokens[index];
234
+ if (token.map) {
235
+ const line = token.map[0];
236
+ token.attrJoin('class', 'line');
237
+ token.attrSet('data-line', line.toString());
238
+ }
239
+ return (originalRenderer)
240
+ // tslint:disable-next-line:no-void-expression
241
+ ? originalRenderer(tokens, index, options, env, self)
242
+ : self.renderToken(tokens, index, options);
243
+ };
244
+ }
245
+ const originalImageRenderer = engine.renderer.rules.image;
246
+ if (originalImageRenderer) {
247
+ engine.renderer.rules.image = (tokens, index, options, env, self) => {
248
+ if (RenderContentParams.is(env)) {
249
+ const documentUri = env.originUri;
250
+ const token = tokens[index];
251
+ if (token.attrs) {
252
+ const srcAttr = token.attrs.find(a => a[0] === 'src');
253
+ if (srcAttr) {
254
+ const href = srcAttr[1];
255
+ srcAttr[1] = this.linkNormalizer.normalizeLink(documentUri, href);
256
+ }
257
+ }
258
+ }
259
+ return originalImageRenderer(tokens, index, options, env, self);
260
+ };
261
+ }
262
+
263
+ const domParser = new DOMParser();
264
+
265
+ const parseDOM = (html: string) =>
266
+ domParser.parseFromString(html, 'text/html').getElementsByTagName('body')[0] as HTMLElement;
267
+
268
+ const modifyDOM = (body: HTMLElement, tag: string, procedure: (element: Element) => void) => {
269
+ const elements = body.getElementsByTagName(tag);
270
+ for (let i = 0; i < elements.length; i++) {
271
+ const element = elements.item(i);
272
+ if (element) {
273
+ procedure(element);
274
+ }
275
+ }
276
+ };
277
+
278
+ const normalizeAllImgSrcInHTML = (html: string, normalizeLink: (link: string) => string) => {
279
+ const body = parseDOM(html);
280
+ modifyDOM(body, 'img', img => {
281
+ const src = img.getAttributeNode('src');
282
+ if (src) {
283
+ src.nodeValue = normalizeLink(src.nodeValue || '');
284
+ }
285
+ });
286
+ return body.innerHTML;
287
+ };
288
+
289
+ for (const name of ['html_block', 'html_inline']) {
290
+ const originalRenderer = engine.renderer.rules[name];
291
+ if (originalRenderer) {
292
+ engine.renderer.rules[name] = (tokens, index, options, env, self) => {
293
+ const currentToken = tokens[index];
294
+ const content = currentToken.content;
295
+ if (content.includes('<img') && RenderContentParams.is(env)) {
296
+ const documentUri = env.originUri;
297
+ currentToken.content = normalizeAllImgSrcInHTML(content, link => this.linkNormalizer.normalizeLink(documentUri, link));
298
+ }
299
+ return originalRenderer(tokens, index, options, env, self);
300
+ };
301
+ }
302
+ }
303
+
304
+ anchor(engine, {});
305
+ }
306
+ return this.engine;
307
+ }
308
+
309
+ }
@@ -1,18 +1,18 @@
1
- /********************************************************************************
2
- * Copyright (C) 2018 TypeFox and others.
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the Eclipse Public License v. 2.0 which is available at
6
- * http://www.eclipse.org/legal/epl-2.0.
7
- *
8
- * This Source Code may also be made available under the following Secondary
9
- * Licenses when the conditions for such availability set forth in the Eclipse
10
- * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- * with the GNU Classpath Exception which is available at
12
- * https://www.gnu.org/software/classpath/license.html.
13
- *
14
- * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
- ********************************************************************************/
16
-
17
- @import "./markdown.css";
18
- @import "./tomorrow.css";
1
+ /********************************************************************************
2
+ * Copyright (C) 2018 TypeFox and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the Eclipse Public License v. 2.0 which is available at
6
+ * http://www.eclipse.org/legal/epl-2.0.
7
+ *
8
+ * This Source Code may also be made available under the following Secondary
9
+ * Licenses when the conditions for such availability set forth in the Eclipse
10
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ * with the GNU Classpath Exception which is available at
12
+ * https://www.gnu.org/software/classpath/license.html.
13
+ *
14
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ ********************************************************************************/
16
+
17
+ @import "./markdown.css";
18
+ @import "./tomorrow.css";