Files
vscode/extensions/markdown-language-features/preview-src/index.ts
Matt Bierner 9cfd597153 Rework markdown preview code to better support markdown preview editors
Splits the preview part of the markdown preview from the dynamic preview management part of things. Static preview swap to preview the active markdown file and don't scroll sync with any other markdown files
2020-04-11 17:30:12 -07:00

199 lines
4.9 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ActiveLineMarker } from './activeLineMarker';
import { onceDocumentLoaded } from './events';
import { createPosterForVsCode } from './messaging';
import { getEditorLineNumberForPageOffset, scrollToRevealSourceLine, getLineElementForFragment } from './scroll-sync';
import { getSettings, getData } from './settings';
import throttle = require('lodash.throttle');
declare let acquireVsCodeApi: any;
let scrollDisabled = true;
const marker = new ActiveLineMarker();
const settings = getSettings();
const vscode = acquireVsCodeApi();
const state = { ...vscode.getState(), ...getData<any>('data-state') };
// Make sure to sync VS Code state here
vscode.setState(state);
const messaging = createPosterForVsCode(vscode);
window.cspAlerter.setPoster(messaging);
window.styleLoadingMonitor.setPoster(messaging);
window.onload = () => {
updateImageSizes();
};
onceDocumentLoaded(() => {
const scrollProgress = state.scrollProgress;
if (typeof scrollProgress === 'number' && !settings.fragment) {
setImmediate(() => {
scrollDisabled = true;
window.scrollTo(0, scrollProgress * document.body.clientHeight);
});
return;
}
if (settings.scrollPreviewWithEditor) {
setImmediate(() => {
// Try to scroll to fragment if available
if (settings.fragment) {
state.fragment = undefined;
vscode.setState(state);
const element = getLineElementForFragment(settings.fragment);
if (element) {
scrollDisabled = true;
scrollToRevealSourceLine(element.line);
}
} else {
if (!isNaN(settings.line!)) {
scrollDisabled = true;
scrollToRevealSourceLine(settings.line!);
}
}
});
}
});
const onUpdateView = (() => {
const doScroll = throttle((line: number) => {
scrollDisabled = true;
scrollToRevealSourceLine(line);
}, 50);
return (line: number) => {
if (!isNaN(line)) {
state.line = line;
doScroll(line);
}
};
})();
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', () => {
scrollDisabled = true;
updateScrollProgress();
updateImageSizes();
}, true);
window.addEventListener('message', event => {
if (event.data.source !== settings.source) {
return;
}
switch (event.data.type) {
case 'onDidChangeTextEditorSelection':
marker.onDidChangeTextEditorSelection(event.data.line);
break;
case 'updateView':
onUpdateView(event.data.line);
break;
}
}, false);
document.addEventListener('dblclick', event => {
if (!settings.doubleClickToSwitchToEditor) {
return;
}
// Ignore clicks on links
for (let node = event.target as HTMLElement; node; node = node.parentNode as HTMLElement) {
if (node.tagName === 'A') {
return;
}
}
const offset = event.pageY;
const line = getEditorLineNumberForPageOffset(offset);
if (typeof line === 'number' && !isNaN(line)) {
messaging.postMessage('didClick', { line: Math.floor(line) });
}
});
const passThroughLinkSchemes = ['http:', 'https:', 'mailto:', 'vscode:', 'vscode-insiders:'];
document.addEventListener('click', event => {
if (!event) {
return;
}
let node: any = event.target;
while (node) {
if (node.tagName && node.tagName === 'A' && node.href) {
if (node.getAttribute('href').startsWith('#')) {
return;
}
// Pass through known schemes
if (passThroughLinkSchemes.some(scheme => node.href.startsWith(scheme))) {
return;
}
const hrefText = node.getAttribute('data-href') || node.getAttribute('href');
// If original link doesn't look like a url, delegate back to VS Code to resolve
if (!/^[a-z\-]+:/i.test(hrefText)) {
messaging.postMessage('openLink', { href: hrefText });
event.preventDefault();
event.stopPropagation();
return;
}
return;
}
node = node.parentNode;
}
}, true);
window.addEventListener('scroll', throttle(() => {
updateScrollProgress();
if (scrollDisabled) {
scrollDisabled = false;
} else {
const line = getEditorLineNumberForPageOffset(window.scrollY);
if (typeof line === 'number' && !isNaN(line)) {
messaging.postMessage('revealLine', { line });
}
}
}, 50));
function updateScrollProgress() {
state.scrollProgress = window.scrollY / document.body.clientHeight;
vscode.setState(state);
}