improve markdown preview scroll sync (#58852)
* improve markdown preview scroll sync
This commit is contained in:
parent
0f4893299a
commit
f8f4d3af30
7 changed files with 112 additions and 11 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -27,6 +27,10 @@ const messaging = createPosterForVsCode(vscode);
|
||||||
window.cspAlerter.setPoster(messaging);
|
window.cspAlerter.setPoster(messaging);
|
||||||
window.styleLoadingMonitor.setPoster(messaging);
|
window.styleLoadingMonitor.setPoster(messaging);
|
||||||
|
|
||||||
|
window.onload = () => {
|
||||||
|
updateImageSizes();
|
||||||
|
};
|
||||||
|
|
||||||
onceDocumentLoaded(() => {
|
onceDocumentLoaded(() => {
|
||||||
if (settings.scrollPreviewWithEditor) {
|
if (settings.scrollPreviewWithEditor) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -53,8 +57,32 @@ const onUpdateView = (() => {
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
let updateImageSizes = throttle(() => {
|
||||||
|
const imageInfo: { id: string, height: number, width: number }[] = [];
|
||||||
|
let images = document.getElementsByTagName('img');
|
||||||
|
if (images) {
|
||||||
|
let i;
|
||||||
|
for (i = 0; i < images.length; i++) {
|
||||||
|
const img = images[i];
|
||||||
|
|
||||||
|
if (img.classList.contains('loading')) {
|
||||||
|
img.classList.remove('loading');
|
||||||
|
}
|
||||||
|
|
||||||
|
imageInfo.push({
|
||||||
|
id: img.id,
|
||||||
|
height: img.height,
|
||||||
|
width: img.width
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
messaging.postMessage('cacheImageSizes', imageInfo);
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
scrollDisabled = true;
|
scrollDisabled = true;
|
||||||
|
updateImageSizes();
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
window.addEventListener('message', event => {
|
window.addEventListener('message', event => {
|
||||||
|
|
|
@ -33,6 +33,7 @@ export class MarkdownPreview {
|
||||||
private forceUpdate = false;
|
private forceUpdate = false;
|
||||||
private isScrolling = false;
|
private isScrolling = false;
|
||||||
private _disposed: boolean = false;
|
private _disposed: boolean = false;
|
||||||
|
private imageInfo: { id: string, width: number, height: number }[] = [];
|
||||||
|
|
||||||
public static async revive(
|
public static async revive(
|
||||||
webview: vscode.WebviewPanel,
|
webview: vscode.WebviewPanel,
|
||||||
|
@ -123,6 +124,10 @@ export class MarkdownPreview {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (e.type) {
|
switch (e.type) {
|
||||||
|
case 'cacheImageSizes':
|
||||||
|
this.onCacheImageSizes(e.body);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'command':
|
case 'command':
|
||||||
vscode.commands.executeCommand(e.body.command, ...e.body.args);
|
vscode.commands.executeCommand(e.body.command, ...e.body.args);
|
||||||
break;
|
break;
|
||||||
|
@ -181,7 +186,8 @@ export class MarkdownPreview {
|
||||||
return {
|
return {
|
||||||
resource: this.resource.toString(),
|
resource: this.resource.toString(),
|
||||||
locked: this._locked,
|
locked: this._locked,
|
||||||
line: this.line
|
line: this.line,
|
||||||
|
imageInfo: this.imageInfo
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,6 +406,10 @@ export class MarkdownPreview {
|
||||||
|
|
||||||
vscode.workspace.openTextDocument(this._resource).then(vscode.window.showTextDocument);
|
vscode.workspace.openTextDocument(this._resource).then(vscode.window.showTextDocument);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async onCacheImageSizes(imageInfo: { id: string, width: number, height: number }[]) {
|
||||||
|
this.imageInfo = imageInfo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PreviewSettings {
|
export interface PreviewSettings {
|
||||||
|
|
|
@ -79,7 +79,7 @@ export class MarkdownContentProvider {
|
||||||
data-strings="${JSON.stringify(previewStrings).replace(/"/g, '"')}"
|
data-strings="${JSON.stringify(previewStrings).replace(/"/g, '"')}"
|
||||||
data-state="${JSON.stringify(state || {}).replace(/"/g, '"')}">
|
data-state="${JSON.stringify(state || {}).replace(/"/g, '"')}">
|
||||||
<script src="${this.extensionResourcePath('pre.js')}" nonce="${nonce}"></script>
|
<script src="${this.extensionResourcePath('pre.js')}" nonce="${nonce}"></script>
|
||||||
${this.getStyles(sourceUri, nonce, config)}
|
${this.getStyles(sourceUri, nonce, config, state)}
|
||||||
<base href="${markdownDocument.uri.with({ scheme: 'vscode-resource' }).toString(true)}">
|
<base href="${markdownDocument.uri.with({ scheme: 'vscode-resource' }).toString(true)}">
|
||||||
</head>
|
</head>
|
||||||
<body class="vscode-body ${config.scrollBeyondLastLine ? 'scrollBeyondLastLine' : ''} ${config.wordWrap ? 'wordWrap' : ''} ${config.markEditorSelection ? 'showEditorSelection' : ''}">
|
<body class="vscode-body ${config.scrollBeyondLastLine ? 'scrollBeyondLastLine' : ''} ${config.wordWrap ? 'wordWrap' : ''} ${config.markEditorSelection ? 'showEditorSelection' : ''}">
|
||||||
|
@ -147,14 +147,30 @@ export class MarkdownContentProvider {
|
||||||
</style>`;
|
</style>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getStyles(resource: vscode.Uri, nonce: string, config: MarkdownPreviewConfiguration): string {
|
private getImageStabilizerStyles(state?: any) {
|
||||||
|
let ret = '<style>\n';
|
||||||
|
if (state && state.imageInfo) {
|
||||||
|
state.imageInfo.forEach((imgInfo: any) => {
|
||||||
|
ret += `#${imgInfo.id}.loading {
|
||||||
|
height: ${imgInfo.height}px;
|
||||||
|
width: ${imgInfo.width}px;
|
||||||
|
}\n`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ret += '</style>\n';
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getStyles(resource: vscode.Uri, nonce: string, config: MarkdownPreviewConfiguration, state?: any): string {
|
||||||
const baseStyles = this.contributions.previewStyles
|
const baseStyles = this.contributions.previewStyles
|
||||||
.map(resource => `<link rel="stylesheet" type="text/css" href="${resource.toString()}">`)
|
.map(resource => `<link rel="stylesheet" type="text/css" href="${resource.toString()}">`)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
|
||||||
return `${baseStyles}
|
return `${baseStyles}
|
||||||
${this.getSettingsOverrideStyles(nonce, config)}
|
${this.getSettingsOverrideStyles(nonce, config)}
|
||||||
${this.computeCustomStyleSheetIncludes(resource, config)}`;
|
${this.computeCustomStyleSheetIncludes(resource, config)}
|
||||||
|
${this.getImageStabilizerStyles(state)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getScripts(nonce: string): string {
|
private getScripts(nonce: string): string {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
import { MarkdownIt, Token } from 'markdown-it';
|
import { MarkdownIt, Token } from 'markdown-it';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
import { MarkdownContributions } from './markdownExtensions';
|
import { MarkdownContributions } from './markdownExtensions';
|
||||||
import { Slugifier } from './slugify';
|
import { Slugifier } from './slugify';
|
||||||
import { getUriForLinkWithKnownExternalScheme } from './util/links';
|
import { getUriForLinkWithKnownExternalScheme } from './util/links';
|
||||||
|
@ -62,6 +63,7 @@ export class MarkdownEngine {
|
||||||
this.addLineNumberRenderer(this.md, renderName);
|
this.addLineNumberRenderer(this.md, renderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.addImageStabilizer(this.md);
|
||||||
this.addFencedRenderer(this.md);
|
this.addFencedRenderer(this.md);
|
||||||
|
|
||||||
this.addLinkNormalizer(this.md);
|
this.addLinkNormalizer(this.md);
|
||||||
|
@ -131,6 +133,28 @@ export class MarkdownEngine {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addImageStabilizer(md: any): void {
|
||||||
|
const original = md.renderer.rules.image;
|
||||||
|
md.renderer.rules.image = (tokens: any, idx: number, options: any, env: any, self: any) => {
|
||||||
|
const token = tokens[idx];
|
||||||
|
token.attrJoin('class', 'loading');
|
||||||
|
|
||||||
|
const src = token.attrGet('src');
|
||||||
|
if (src) {
|
||||||
|
const hash = crypto.createHash('sha256');
|
||||||
|
hash.update(src);
|
||||||
|
const imgHash = hash.digest('hex');
|
||||||
|
token.attrSet('id', `image-hash-${imgHash}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (original) {
|
||||||
|
return original(tokens, idx, options, env, self);
|
||||||
|
} else {
|
||||||
|
return self.renderToken(tokens, idx, options, env, self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private addFencedRenderer(md: any): void {
|
private addFencedRenderer(md: any): void {
|
||||||
const original = md.renderer.rules['fenced'];
|
const original = md.renderer.rules['fenced'];
|
||||||
md.renderer.rules['fenced'] = (tokens: any, idx: number, options: any, env: any, self: any) => {
|
md.renderer.rules['fenced'] = (tokens: any, idx: number, options: any, env: any, self: any) => {
|
||||||
|
|
|
@ -308,21 +308,21 @@
|
||||||
|
|
||||||
const frame = getActiveFrame();
|
const frame = getActiveFrame();
|
||||||
|
|
||||||
// keep current scrollTop around and use later
|
// keep current scrollY around and use later
|
||||||
var setInitialScrollPosition;
|
var setInitialScrollPosition;
|
||||||
if (firstLoad) {
|
if (firstLoad) {
|
||||||
firstLoad = false;
|
firstLoad = false;
|
||||||
setInitialScrollPosition = (body, window) => {
|
setInitialScrollPosition = (body, window) => {
|
||||||
if (!isNaN(initData.initialScrollProgress)) {
|
if (!isNaN(initData.initialScrollProgress)) {
|
||||||
if (body.scrollTop === 0) {
|
if (window.scrollY === 0) {
|
||||||
window.scroll(0, body.clientHeight * initData.initialScrollProgress);
|
window.scroll(0, body.clientHeight * initData.initialScrollProgress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const scrollY = frame && frame.contentDocument && frame.contentDocument.body ? frame.contentDocument.body.scrollTop : 0;
|
const scrollY = frame && frame.contentDocument && frame.contentDocument.body ? frame.contentWindow.scrollY : 0;
|
||||||
setInitialScrollPosition = (body, window) => {
|
setInitialScrollPosition = (body, window) => {
|
||||||
if (body.scrollTop === 0) {
|
if (window.scrollY === 0) {
|
||||||
window.scroll(0, scrollY);
|
window.scroll(0, scrollY);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -359,7 +359,7 @@
|
||||||
var onLoad = (contentDocument, contentWindow) => {
|
var onLoad = (contentDocument, contentWindow) => {
|
||||||
if (contentDocument.body) {
|
if (contentDocument.body) {
|
||||||
// Workaround for https://github.com/Microsoft/vscode/issues/12865
|
// Workaround for https://github.com/Microsoft/vscode/issues/12865
|
||||||
// check new scrollTop and reset if neccessary
|
// check new scrollY and reset if neccessary
|
||||||
setInitialScrollPosition(contentDocument.body, contentWindow);
|
setInitialScrollPosition(contentDocument.body, contentWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue