Remove the built-in markdown rendering for notebooks (#128806)

This switches our notebooks to always use contributed markdown rendering instead of our built-in markdown renderer

We'd held off on switching over due to accessibility. I've tried to address this using the `aria-describedby` attributed to link the markdown container with a copy of rendered (and sanitized) html from the webview.
This commit is contained in:
Matt Bierner 2021-07-16 15:44:04 -07:00 committed by GitHub
parent cf77875d5c
commit 27858a0faf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 83 additions and 241 deletions

View file

@ -1392,7 +1392,7 @@ const _ttpSafeInnerHtml = window.trustedTypes?.createPolicy('safeInnerHtml', {
export function safeInnerHtml(node: HTMLElement, value: string): void {
const options = _extInsaneOptions({
allowedTags: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'],
allowedTags: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'],
allowedAttributes: {
'a': ['href', 'x-dispatch'],
'button': ['data-href', 'x-dispatch'],

View file

@ -30,7 +30,7 @@ import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEd
import { isCompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl';
import { CellKind, CellToolbarLocation, CellToolbarVisibility, CellUri, DisplayOrderKey, UndoRedoPerCell, ExperimentalUseMarkdownRenderer, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookTextDiffEditorPreview, NotebookWorkingCopyTypeIdentifier, ShowCellStatusBar, CompactView, FocusIndicator, InsertToolbarLocation, GlobalToolbar, ConsolidatedOutputButton, ShowFoldingControls, DragAndDropEnabled, NotebookCellEditorOptionsCustomizations, ConsolidatedRunButton } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellKind, CellToolbarLocation, CellToolbarVisibility, CellUri, DisplayOrderKey, UndoRedoPerCell, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookTextDiffEditorPreview, NotebookWorkingCopyTypeIdentifier, ShowCellStatusBar, CompactView, FocusIndicator, InsertToolbarLocation, GlobalToolbar, ConsolidatedOutputButton, ShowFoldingControls, DragAndDropEnabled, NotebookCellEditorOptionsCustomizations, ConsolidatedRunButton } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
@ -651,11 +651,6 @@ configurationRegistry.registerConfiguration({
type: 'boolean',
default: true
},
[ExperimentalUseMarkdownRenderer]: {
description: nls.localize('notebook.experimental.useMarkdownRenderer.description', "Enable/disable using the new extensible markdown renderer."),
type: 'boolean',
default: true
},
[CellToolbarVisibility]: {
markdownDescription: nls.localize('notebook.cellToolbarVisibility.description', "Whether the cell toolbar should appear on hover or click."),
type: 'string',

View file

@ -749,7 +749,6 @@ export interface MarkdownCellRenderTemplate extends BaseCellRenderTemplate {
foldingIndicator: HTMLElement;
focusIndicatorBottom: HTMLElement;
currentEditor?: ICodeEditor;
readonly useRenderer: boolean;
}
export interface CodeCellRenderTemplate extends BaseCellRenderTemplate {

View file

@ -33,7 +33,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { contrastBorder, diffInserted, diffRemoved, editorBackground, errorForeground, focusBorder, foreground, listInactiveSelectionBackground, registerColor, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground, transparent } from 'vs/platform/theme/common/colorRegistry';
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
@ -55,11 +54,10 @@ import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbenc
import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel';
import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellKind, ExperimentalUseMarkdownRenderer, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator';
import { Webview } from 'vs/workbench/contrib/webview/browser/webview';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance';
import { readFontInfo } from 'vs/editor/browser/config/configuration';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
@ -248,8 +246,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
private _isDisposed: boolean = false;
private useRenderer = false;
get isDisposed() {
return this._isDisposed;
}
@ -322,8 +318,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
constructor(
readonly creationOptions: INotebookEditorCreationOptions,
@IInstantiationService instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@IAccessibilityService accessibilityService: IAccessibilityService,
@INotebookRendererMessagingService private readonly notebookRendererMessaging: INotebookRendererMessagingService,
@INotebookEditorService private readonly notebookEditorService: INotebookEditorService,
@INotebookKernelService private readonly notebookKernelService: INotebookKernelService,
@ -340,7 +334,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this.isEmbedded = creationOptions.isEmbedded ?? false;
this._readOnly = creationOptions.isReadOnly ?? false;
this.useRenderer = !!this.configurationService.getValue<boolean>(ExperimentalUseMarkdownRenderer) && !accessibilityService.isScreenReaderOptimized();
this._notebookOptions = new NotebookOptions(this.configurationService);
this._register(this._notebookOptions);
this._viewContext = new ViewContext(this._notebookOptions, new NotebookEventDispatcher());
@ -805,7 +798,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
const getScopedContextKeyService = (container: HTMLElement) => this._list.contextKeyService.createScoped(container);
const renderers = [
this.instantiationService.createInstance(CodeCellRenderer, this, this._renderedEditors, this._dndController, getScopedContextKeyService),
this.instantiationService.createInstance(MarkupCellRenderer, this, this._dndController, this._renderedEditors, getScopedContextKeyService, { useRenderer: this.useRenderer }),
this.instantiationService.createInstance(MarkupCellRenderer, this, this._dndController, this._renderedEditors, getScopedContextKeyService),
];
renderers.forEach(renderer => {
@ -1290,11 +1283,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}));
// init rendering
if (this.useRenderer) {
await this._warmupWithMarkdownRenderer(this.viewModel, viewState);
} else {
this._list.attachViewModel(this.viewModel);
}
await this._warmupWithMarkdownRenderer(this.viewModel, viewState);
mark(textModel.uri, 'customMarkdownLoaded');
@ -2286,11 +2275,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}
async createMarkupPreview(cell: MarkupCellViewModel) {
if (!this.useRenderer) {
// TODO: handle case where custom renderer is disabled?
return;
}
if (!this._webview) {
return;
}
@ -2315,11 +2299,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}
async unhideMarkupPreviews(cells: readonly MarkupCellViewModel[]) {
if (!this.useRenderer) {
// TODO: handle case where custom renderer is disabled?
return;
}
if (!this._webview) {
return;
}
@ -2332,11 +2311,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}
async hideMarkupPreviews(cells: readonly MarkupCellViewModel[]) {
if (!this.useRenderer) {
// TODO: handle case where custom renderer is disabled?
return;
}
if (!this._webview || !cells.length) {
return;
}
@ -2349,11 +2323,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}
async deleteMarkupPreviews(cells: readonly MarkupCellViewModel[]) {
if (!this.useRenderer) {
// TODO: handle case where custom renderer is disabled?
return;
}
if (!this._webview) {
return;
}
@ -2366,7 +2335,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}
private async updateSelectedMarkdownPreviews(): Promise<void> {
if (!this.useRenderer || !this._webview) {
if (!this._webview) {
return;
}

View file

@ -580,7 +580,14 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
this.notebookEditor.didEndDragMarkupCell(data.cellId);
break;
}
case 'renderedMarkup':
{
const cell = this.notebookEditor.getCellById(data.cellId);
if (cell instanceof MarkupCellViewModel) {
cell.renderedHtml = data.html;
}
break;
}
case 'telemetryFoundRenderedMarkdownMath':
{
this.telemetryService.publicLog2<{}, {}>('notebook/markdown/renderedLatex', {});

View file

@ -40,7 +40,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { syncing } from 'vs/platform/theme/common/iconRegistry';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { DeleteCellAction, INotebookActionContext, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions';
import { BaseCellRenderTemplate, CellEditState, CodeCellLayoutInfo, CodeCellRenderTemplate, EXPAND_CELL_INPUT_COMMAND_ID, ICellViewModel, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { BaseCellRenderTemplate, CodeCellLayoutInfo, CodeCellRenderTemplate, EXPAND_CELL_INPUT_COMMAND_ID, ICellViewModel, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { errorStateIcon, successStateIcon, unfoldIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView';
import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys';
@ -302,14 +302,11 @@ abstract class AbstractCellRenderer {
export class MarkupCellRenderer extends AbstractCellRenderer implements IListRenderer<MarkupCellViewModel, MarkdownCellRenderTemplate> {
static readonly TEMPLATE_ID = 'markdown_cell';
private readonly useRenderer: boolean;
constructor(
notebookEditor: INotebookEditor,
dndController: CellDragAndDropController,
private renderedEditors: Map<ICellViewModel, ICodeEditor | undefined>,
contextKeyServiceProvider: (container: HTMLElement) => IContextKeyService,
options: { useRenderer: boolean; },
@IConfigurationService private configurationService: IConfigurationService,
@IInstantiationService instantiationService: IInstantiationService,
@IContextMenuService contextMenuService: IContextMenuService,
@ -318,7 +315,6 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen
@INotificationService notificationService: INotificationService,
) {
super(instantiationService, notebookEditor, contextMenuService, menuService, configurationService, keybindingService, notificationService, contextKeyServiceProvider, 'markdown', dndController);
this.useRenderer = options.useRenderer;
}
get templateId() {
@ -361,7 +357,6 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen
const titleMenu = disposables.add(this.menuService.createMenu(this.notebookEditor.creationOptions.menuIds.cellTitleToolbar, contextKeyService));
const templateData: MarkdownCellRenderTemplate = {
useRenderer: this.useRenderer,
rootContainer,
collapsedPart,
expandButton,
@ -386,43 +381,11 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen
toJSON: () => { return {}; }
};
if (!this.useRenderer) {
this.dndController?.registerDragHandle(templateData, rootContainer, container, () => this.getDragImage(templateData));
}
this.commonRenderTemplate(templateData);
return templateData;
}
private getDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement {
if (templateData.currentRenderedCell?.getEditState() === CellEditState.Editing) {
return this.getEditDragImage(templateData);
} else {
return this.getMarkdownDragImage(templateData);
}
}
private getMarkdownDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement {
const dragImageContainer = DOM.$('.cell-drag-image.monaco-list-row.focused.markdown-cell-row');
DOM.reset(dragImageContainer, templateData.container.cloneNode(true));
// Remove all rendered content nodes after the
const markdownContent = dragImageContainer.querySelector('.cell.markdown');
const contentNodes = markdownContent?.children[0].children;
if (contentNodes) {
for (let i = contentNodes.length - 1; i >= 1; i--) {
contentNodes.item(i)!.remove();
}
}
return dragImageContainer;
}
private getEditDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement {
return new CodeCellDragImageRenderer().getDragImage(templateData, templateData.currentEditor!, 'markdown');
}
renderElement(element: MarkupCellViewModel, index: number, templateData: MarkdownCellRenderTemplate, height: number | undefined): void {
if (!this.notebookEditor.hasModel()) {
throw new Error('The notebook editor is not attached with view model yet.');
@ -511,7 +474,7 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen
this.setBetweenCellToolbarContext(templateData, element, toolbarContext);
const scopedInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, templateData.contextKeyService]));
const markdownCell = scopedInstaService.createInstance(StatefulMarkdownCell, this.notebookEditor, element, templateData, cellEditorOptions.getValue(element.internalMetadata), this.renderedEditors, { useRenderer: templateData.useRenderer });
const markdownCell = scopedInstaService.createInstance(StatefulMarkdownCell, this.notebookEditor, element, templateData, cellEditorOptions.getValue(element.internalMetadata), this.renderedEditors,);
elementDisposables.add(markdownCell);
elementDisposables.add(cellEditorOptions.onDidChange(newValue => markdownCell.updateEditorOptions(cellEditorOptions.getValue(element.internalMetadata))));

View file

@ -6,7 +6,7 @@
import * as DOM from 'vs/base/browser/dom';
import { disposableTimeout, raceCancellation } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@ -17,100 +17,22 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets';
import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService';
import { collapsedIcon, expandedIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
interface IMarkdownRenderStrategy extends IDisposable {
update(): void;
}
class WebviewMarkdownRenderer extends Disposable implements IMarkdownRenderStrategy {
constructor(
readonly notebookEditor: IActiveNotebookEditor,
readonly viewCell: MarkupCellViewModel
) {
super();
}
update(): void {
this.notebookEditor.createMarkupPreview(this.viewCell);
}
}
class BuiltinMarkdownRenderer extends Disposable implements IMarkdownRenderStrategy {
private readonly localDisposables = this._register(new DisposableStore());
constructor(
private readonly notebookEditor: IActiveNotebookEditor,
private readonly viewCell: MarkupCellViewModel,
private readonly container: HTMLElement,
private readonly markdownContainer: HTMLElement,
private readonly editorAccessor: () => CodeEditorWidget | null
) {
super();
this._register(getResizesObserver(this.markdownContainer, undefined, () => {
if (viewCell.getEditState() === CellEditState.Preview) {
this.viewCell.renderedMarkdownHeight = container.clientHeight;
}
})).startObserving();
}
update(): void {
const markdownRenderer = this.viewCell.getMarkdownRenderer();
const renderedHTML = this.viewCell.getHTML();
if (renderedHTML) {
this.markdownContainer.appendChild(renderedHTML);
}
if (this.editorAccessor()) {
// switch from editing mode
this.viewCell.renderedMarkdownHeight = this.container.clientHeight;
this.relayoutCell();
} else {
this.localDisposables.clear();
this.localDisposables.add(markdownRenderer.onDidRenderAsync(() => {
if (this.viewCell.getEditState() === CellEditState.Preview) {
this.viewCell.renderedMarkdownHeight = this.container.clientHeight;
}
this.relayoutCell();
}));
this.localDisposables.add(this.viewCell.textBuffer.onDidChangeContent(() => {
this.markdownContainer.innerText = '';
this.viewCell.clearHTML();
const renderedHTML = this.viewCell.getHTML();
if (renderedHTML) {
this.markdownContainer.appendChild(renderedHTML);
}
}));
this.viewCell.renderedMarkdownHeight = this.container.clientHeight;
this.relayoutCell();
}
}
relayoutCell() {
this.notebookEditor.layoutNotebookCell(this.viewCell, this.viewCell.layoutInfo.totalHeight);
}
}
export class StatefulMarkdownCell extends Disposable {
private editor: CodeEditorWidget | null = null;
private markdownContainer: HTMLElement;
private markdownAccessibilityContainer: HTMLElement;
private editorPart: HTMLElement;
private readonly localDisposables = this._register(new DisposableStore());
private readonly focusSwitchDisposable = this._register(new MutableDisposable());
private readonly editorDisposables = this._register(new DisposableStore());
private foldingState: CellFoldingState;
private useRenderer: boolean = false;
private renderStrategy: IMarkdownRenderStrategy;
constructor(
private readonly notebookEditor: IActiveNotebookEditor,
@ -118,25 +40,28 @@ export class StatefulMarkdownCell extends Disposable {
private readonly templateData: MarkdownCellRenderTemplate,
private editorOptions: IEditorOptions,
private readonly renderedEditors: Map<ICellViewModel, ICodeEditor | undefined>,
options: { useRenderer: boolean; },
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@INotebookCellStatusBarService readonly notebookCellStatusBarService: INotebookCellStatusBarService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
this.markdownContainer = templateData.cellContainer;
// Create an element that is only used to announce markup cell content to screen readers
const id = `aria-markup-cell-${this.viewCell.id}`;
this.markdownAccessibilityContainer = templateData.cellContainer;
this.markdownAccessibilityContainer.id = id;
// Hide the element from non-screen readers
this.markdownAccessibilityContainer.style.height = '1px';
this.markdownAccessibilityContainer.style.position = 'absolute';
this.markdownAccessibilityContainer.style.top = '10000px';
this.markdownAccessibilityContainer.ariaHidden = 'false';
this.templateData.rootContainer.setAttribute('aria-describedby', id);
this.editorPart = templateData.editorPart;
this.useRenderer = options.useRenderer;
if (this.useRenderer) {
this.templateData.container.classList.toggle('webview-backed-markdown-cell', true);
this.renderStrategy = new WebviewMarkdownRenderer(this.notebookEditor, this.viewCell);
} else {
this.renderStrategy = new BuiltinMarkdownRenderer(this.notebookEditor, this.viewCell, this.templateData.container, this.markdownContainer, () => this.editor);
}
this.templateData.container.classList.toggle('webview-backed-markdown-cell', true);
this._register(this.renderStrategy);
this._register(toDisposable(() => renderedEditors.delete(this.viewCell)));
this._register(viewCell.onDidChangeState((e) => {
@ -199,11 +124,9 @@ export class StatefulMarkdownCell extends Disposable {
}
}));
if (this.useRenderer) {
// the markdown preview's height might already be updated after the renderer calls `element.getHeight()`
if (this.viewCell.layoutInfo.totalHeight > 0) {
this.relayoutCell();
}
// the markdown preview's height might already be updated after the renderer calls `element.getHeight()`
if (this.viewCell.layoutInfo.totalHeight > 0) {
this.relayoutCell();
}
// apply decorations
@ -211,32 +134,20 @@ export class StatefulMarkdownCell extends Disposable {
this._register(viewCell.onCellDecorationsChanged((e) => {
e.added.forEach(options => {
if (options.className) {
if (this.useRenderer) {
this.notebookEditor.deltaCellOutputContainerClassNames(this.viewCell.id, [options.className], []);
} else {
templateData.rootContainer.classList.add(options.className);
}
this.notebookEditor.deltaCellOutputContainerClassNames(this.viewCell.id, [options.className], []);
}
});
e.removed.forEach(options => {
if (options.className) {
if (this.useRenderer) {
this.notebookEditor.deltaCellOutputContainerClassNames(this.viewCell.id, [], [options.className]);
} else {
templateData.rootContainer.classList.remove(options.className);
}
this.notebookEditor.deltaCellOutputContainerClassNames(this.viewCell.id, [], [options.className]);
}
});
}));
viewCell.getCellDecorations().forEach(options => {
if (options.className) {
if (this.useRenderer) {
this.notebookEditor.deltaCellOutputContainerClassNames(this.viewCell.id, [options.className], []);
} else {
templateData.rootContainer.classList.add(options.className);
}
this.notebookEditor.deltaCellOutputContainerClassNames(this.viewCell.id, [options.className], []);
}
});
@ -267,7 +178,8 @@ export class StatefulMarkdownCell extends Disposable {
private viewUpdateCollapsed(): void {
DOM.show(this.templateData.collapsedPart);
DOM.hide(this.editorPart);
DOM.hide(this.markdownContainer);
this.markdownAccessibilityContainer.ariaHidden = 'true';
this.templateData.container.classList.toggle('collapsed', true);
this.viewCell.renderedMarkdownHeight = 0;
this.viewCell.layoutChange({});
@ -278,12 +190,10 @@ export class StatefulMarkdownCell extends Disposable {
let editorHeight: number;
DOM.show(this.editorPart);
DOM.hide(this.markdownContainer);
this.markdownAccessibilityContainer.ariaHidden = 'true';
DOM.hide(this.templateData.collapsedPart);
if (this.useRenderer) {
this.notebookEditor.hideMarkupPreviews([this.viewCell]);
}
this.notebookEditor.hideMarkupPreviews([this.viewCell]);
this.templateData.container.classList.toggle('collapsed', false);
this.templateData.container.classList.toggle('markdown-cell-edit-mode', true);
@ -371,16 +281,18 @@ export class StatefulMarkdownCell extends Disposable {
this.viewCell.detachTextEditor();
DOM.hide(this.editorPart);
DOM.hide(this.templateData.collapsedPart);
DOM.show(this.markdownContainer);
this.markdownAccessibilityContainer.ariaHidden = 'false';
this.templateData.container.classList.toggle('collapsed', false);
this.templateData.container.classList.toggle('markdown-cell-edit-mode', false);
this.renderedEditors.delete(this.viewCell);
this.markdownContainer.innerText = '';
this.viewCell.clearHTML();
this.markdownAccessibilityContainer.innerText = '';
if (this.viewCell.renderedHtml) {
DOM.safeInnerHtml(this.markdownAccessibilityContainer, this.viewCell.renderedHtml);
}
this.renderStrategy.update();
this.notebookEditor.createMarkupPreview(this.viewCell);
}
private focusEditorIfNeeded() {

View file

@ -129,6 +129,12 @@ export interface IInitializedMarkupMessage extends BaseToWebviewMessage {
readonly type: 'initializedMarkup';
}
export interface IRenderedMarkupMessage extends BaseToWebviewMessage {
readonly type: 'renderedMarkup';
readonly cellId: string;
readonly html: string;
}
export interface ITelemetryFoundRenderedMarkdownMath extends BaseToWebviewMessage {
readonly type: 'telemetryFoundRenderedMarkdownMath';
}
@ -342,6 +348,7 @@ export type FromWebviewMessage = WebviewIntialized |
ICellDropMessage |
ICellDragEndMessage |
IInitializedMarkupMessage |
IRenderedMarkupMessage |
ITelemetryFoundRenderedMarkdownMath |
ITelemetryFoundUnrenderedMarkdownMath;

View file

@ -1130,6 +1130,27 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
}
}
const root = (this.element.shadowRoot ?? this.element);
const html = [];
for (const child of root.children) {
switch (child.tagName) {
case 'LINK':
case 'SCRIPT':
case 'STYLE':
// not worth sending over since it will be stripped before rendering
break;
default:
html.push(child.outerHTML);
break;
}
}
postNotebookMessage<webviewMessages.IRenderedMarkupMessage>('renderedMarkup', {
cellId: this.id,
html: html.join(''),
});
dimensionUpdater.updateHeight(this.id, this.element.offsetHeight, {
isOutput: false
});

View file

@ -6,11 +6,9 @@
import { Emitter, Event } from 'vs/base/common/event';
import * as UUID from 'vs/base/common/uuid';
import * as editorCommon from 'vs/editor/common/editorCommon';
import * as nls from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { EditorFoldingStateDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel';
import { CellEditState, CellFindMatch, ICellOutputViewModel, ICellViewModel, MarkdownCellLayoutChangeEvent, MarkdownCellLayoutInfo, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer';
import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel';
import { NotebookCellStateChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
@ -18,15 +16,21 @@ import { CellKind, INotebookSearchOptions } from 'vs/workbench/contrib/notebook/
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { dirname } from 'vs/base/common/resources';
export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewModel {
readonly cellKind = CellKind.Markup;
private _html: HTMLElement | null = null;
private _layoutInfo: MarkdownCellLayoutInfo;
private _renderedHtml?: string;
public get renderedHtml(): string | undefined { return this._renderedHtml; }
public set renderedHtml(value: string | undefined) {
this._renderedHtml = value;
this._onDidChangeState.fire({ contentChanged: true });
}
get layoutInfo() {
return this._layoutInfo;
}
@ -101,8 +105,6 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM
private readonly _onDidHideInput = new Emitter<void>();
readonly onDidHideInput = this._onDidHideInput.event;
private readonly _mdRenderer: MarkdownRenderer;
constructor(
viewType: string,
model: NotebookCellTextModel,
@ -115,8 +117,6 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM
) {
super(viewType, model, UUID.generateUuid(), viewContext, configurationService, textModelService);
this._mdRenderer = this._register(instantiationService.createInstance(MarkdownRenderer, { baseUrl: dirname(model.uri) }));
const { bottomToolbarGap } = this.viewContext.notebookOptions.computeBottomToolbarDimensions(this.viewType);
this._layoutInfo = {
@ -246,43 +246,13 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM
}
}
clearHTML() {
this._html = null;
}
getHTML(): HTMLElement | null {
if (this.cellKind === CellKind.Markup) {
if (this._html) {
return this._html;
}
const renderer = this.getMarkdownRenderer();
const text = this.getText();
if (text.length === 0) {
const el = document.createElement('p');
el.className = 'emptyMarkdownPlaceholder';
el.innerText = nls.localize('notebook.emptyMarkdownPlaceholder', "Empty markdown cell, double click or press enter to edit.");
this._html = el;
} else {
this._html = renderer.render({ value: this.getText(), isTrusted: true }, undefined, { gfm: true }).element;
}
return this._html;
}
return null;
}
protected onDidChangeTextModelContent(): void {
this._html = null;
this._onDidChangeState.fire({ contentChanged: true });
}
onDeselect() {
}
getMarkdownRenderer() {
return this._mdRenderer;
}
private readonly _hasFindResult = this._register(new Emitter<boolean>());
public readonly hasFindResult: Event<boolean> = this._hasFindResult.event;

View file

@ -838,7 +838,6 @@ export const CellToolbarVisibility = 'notebook.cellToolbarVisibility';
export type ShowCellStatusBarType = 'hidden' | 'visible' | 'visibleAfterExecute';
export const ShowCellStatusBar = 'notebook.showCellStatusBar';
export const NotebookTextDiffEditorPreview = 'notebook.diff.enablePreview';
export const ExperimentalUseMarkdownRenderer = 'notebook.experimental.useMarkdownRenderer';
export const ExperimentalInsertToolbarAlignment = 'notebook.experimental.insertToolbarAlignment';
export const CompactView = 'notebook.compactView';
export const FocusIndicator = 'notebook.cellFocusIndicator';