improve markdown preview scroll sync (#58852)

* improve markdown preview scroll sync
This commit is contained in:
SteVen Batten 2018-09-18 15:08:37 -07:00 committed by GitHub
parent 0f4893299a
commit f8f4d3af30
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
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

View file

@ -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 => {

View file

@ -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 {

View file

@ -79,7 +79,7 @@ export class MarkdownContentProvider {
data-strings="${JSON.stringify(previewStrings).replace(/"/g, '&quot;')}" data-strings="${JSON.stringify(previewStrings).replace(/"/g, '&quot;')}"
data-state="${JSON.stringify(state || {}).replace(/"/g, '&quot;')}"> data-state="${JSON.stringify(state || {}).replace(/"/g, '&quot;')}">
<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 {

View file

@ -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) => {

View file

@ -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);
} }