mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-31 17:23:31 -05:00
moves notebooks code to browser (#7313)
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
https://raw.githubusercontent.com/isagalaev/highlight.js/master/src/styles/vs2015.css
|
||||
*/
|
||||
/*
|
||||
* Visual Studio 2015 dark style
|
||||
* Author: Nicolas LLOBERA <nllobera@gmail.com>
|
||||
*/
|
||||
|
||||
|
||||
.notebook-preview .hljs-keyword,
|
||||
.notebook-preview .hljs-literal,
|
||||
.notebook-preview .hljs-symbol,
|
||||
.notebook-preview .hljs-name {
|
||||
color: #569CD6;
|
||||
}
|
||||
.notebook-preview .hljs-link {
|
||||
color: #569CD6;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-built_in,
|
||||
.notebook-preview .hljs-type {
|
||||
color: #4EC9B0;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-number,
|
||||
.notebook-preview .hljs-class {
|
||||
color: #B8D7A3;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-string,
|
||||
.notebook-preview .hljs-meta-string {
|
||||
color: #D69D85;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-regexp,
|
||||
.notebook-preview .hljs-template-tag {
|
||||
color: #9A5334;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-subst,
|
||||
.notebook-preview .hljs-function,
|
||||
.notebook-preview .hljs-title,
|
||||
.notebook-preview .hljs-params,
|
||||
.notebook-preview .hljs-formula {
|
||||
color: #DCDCDC;
|
||||
}
|
||||
|
||||
.notebook-preview pre code .hljs-subst,
|
||||
.notebook-preview pre code .hljs-function,
|
||||
.notebook-preview pre code .hljs-title,
|
||||
.notebook-preview pre code .hljs-params,
|
||||
.notebook-preview pre code .hljs-formula {
|
||||
color: var(--vscode-editor-foreground);
|
||||
}
|
||||
|
||||
|
||||
.notebook-preview .hljs-comment,
|
||||
.notebook-preview .hljs-quote {
|
||||
color: #57A64A;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-doctag {
|
||||
color: #608B4E;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-meta,
|
||||
.notebook-preview .hljs-meta-keyword,
|
||||
.notebook-preview .hljs-tag {
|
||||
color: #9B9B9B;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-variable,
|
||||
.notebook-preview .hljs-template-variable {
|
||||
color: #BD63C5;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-attr,
|
||||
.notebook-preview .hljs-attribute,
|
||||
.notebook-preview .hljs-builtin-name {
|
||||
color: #9CDCFE;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-section {
|
||||
color: gold;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/*.hljs-code {
|
||||
font-family:'Monospace';
|
||||
}*/
|
||||
|
||||
.notebook-preview .hljs-bullet,
|
||||
.notebook-preview .hljs-selector-tag,
|
||||
.notebook-preview .hljs-selector-id,
|
||||
.notebook-preview .hljs-selector-class,
|
||||
.notebook-preview .hljs-selector-attr,
|
||||
.notebook-preview .hljs-selector-pseudo {
|
||||
color: #D7BA7D;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-addition {
|
||||
background-color: var(--vscode-diffEditor-insertedTextBackground, rgba(155, 185, 85, 0.2));
|
||||
color: rgb(155, 185, 85);
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-deletion {
|
||||
background: var(--vscode-diffEditor-removedTextBackground, rgba(255, 0, 0, 0.2));
|
||||
color: rgb(255, 0, 0);
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
From https://raw.githubusercontent.com/isagalaev/highlight.js/master/src/styles/vs.css
|
||||
*/
|
||||
/*
|
||||
|
||||
Visual Studio-like style based on original C# coloring by Jason Diamond <jason@diamond.name>
|
||||
|
||||
*/
|
||||
|
||||
.notebook-preview .hljs-function,
|
||||
.notebook-preview .hljs-params {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-comment,
|
||||
.notebook-preview .hljs-quote,
|
||||
.notebook-preview .hljs-variable {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-keyword,
|
||||
.notebook-preview .hljs-selector-tag,
|
||||
.notebook-preview .hljs-built_in,
|
||||
.notebook-preview .hljs-name,
|
||||
.notebook-preview .hljs-tag {
|
||||
color: #00f;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-string,
|
||||
.notebook-preview .hljs-title,
|
||||
.notebook-preview .hljs-section,
|
||||
.notebook-preview .hljs-attribute,
|
||||
.notebook-preview .hljs-literal,
|
||||
.notebook-preview .hljs-template-tag,
|
||||
.notebook-preview .hljs-template-variable,
|
||||
.notebook-preview .hljs-type {
|
||||
color: #a31515;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-selector-attr,
|
||||
.notebook-preview .hljs-selector-pseudo,
|
||||
.notebook-preview .hljs-meta {
|
||||
color: #2b91af;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-doctag {
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-attr {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-symbol,
|
||||
.notebook-preview .hljs-bullet,
|
||||
.notebook-preview .hljs-link {
|
||||
color: #00b0e8;
|
||||
}
|
||||
|
||||
|
||||
.notebook-preview .hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.notebook-preview .hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.notebook-preview {
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.notebook-preview #code-csp-warning {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
color: white;
|
||||
margin: 16px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-family: sans-serif;
|
||||
background-color:#444444;
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
box-shadow: 1px 1px 1px rgba(0,0,0,.25);
|
||||
}
|
||||
|
||||
.notebook-preview #code-csp-warning:hover {
|
||||
text-decoration: none;
|
||||
background-color:#007acc;
|
||||
box-shadow: 2px 2px 2px rgba(0,0,0,.25);
|
||||
}
|
||||
|
||||
|
||||
.notebook-preview .scrollBeyondLastLine {
|
||||
margin-bottom: calc(100vh - 22px);
|
||||
}
|
||||
|
||||
.notebook-preview .showEditorSelection .code-line {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.notebook-preview .showEditorSelection .code-active-line:before,
|
||||
.notebook-preview .showEditorSelection .code-line:hover:before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -12px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.notebook-preview .showEditorSelection li.code-active-line:before,
|
||||
.notebook-preview .showEditorSelection li.code-line:hover:before {
|
||||
left: -30px;
|
||||
}
|
||||
|
||||
.notebook-preview .showEditorSelection .code-active-line:before {
|
||||
border-left: 3px solid rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.notebook-preview .showEditorSelection .code-line:hover:before {
|
||||
border-left: 3px solid rgba(0, 0, 0, 0.40);
|
||||
}
|
||||
|
||||
.notebook-preview .showEditorSelection .code-line .code-line:hover:before {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.vs-dark .notebook-preview .showEditorSelection .code-active-line:before {
|
||||
border-left: 3px solid rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.vs-dark .notebook-preview .showEditorSelection .code-line:hover:before {
|
||||
border-left: 3px solid rgba(255, 255, 255, 0.60);
|
||||
}
|
||||
|
||||
.vs-dark .notebook-preview .showEditorSelection .code-line .code-line:hover:before {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.hc-black .notebook-preview .showEditorSelection .code-active-line:before {
|
||||
border-left: 3px solid rgba(255, 160, 0, 0.7);
|
||||
}
|
||||
|
||||
.hc-black .notebook-preview .showEditorSelection .code-line:hover:before {
|
||||
border-left: 3px solid rgba(255, 160, 0, 1);
|
||||
}
|
||||
|
||||
.hc-black .notebook-preview .showEditorSelection .code-line .code-line:hover:before {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.notebook-preview img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.notebookEditor a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.notebookEditor a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.notebook-preview a:focus,
|
||||
.notebook-preview input:focus,
|
||||
.notebook-preview select:focus,
|
||||
.notebook-preview textarea:focus {
|
||||
outline: 1px solid -webkit-focus-ring-color;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.notebook-preview hr {
|
||||
border: 0;
|
||||
height: 2px;
|
||||
border-bottom: 2px solid;
|
||||
}
|
||||
|
||||
.notebook-preview h1 {
|
||||
padding-bottom: 0.3em;
|
||||
line-height: 1.2;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
.notebook-preview h1, .notebook-preview h2, .notebook-preview h3 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.notebook-preview h1 code,
|
||||
.notebook-preview h2 code,
|
||||
.notebook-preview h3 code,
|
||||
.notebook-preview h4 code,
|
||||
.notebook-preview h5 code,
|
||||
.notebook-preview h6 code {
|
||||
font-size: inherit;
|
||||
line-height: auto;
|
||||
}
|
||||
|
||||
.notebook-preview table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.notebook-preview table > thead > tr > th {
|
||||
text-align: left;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
.notebook-preview table > thead > tr > th,
|
||||
.notebook-preview table > thead > tr > td,
|
||||
.notebook-preview table > tbody > tr > th,
|
||||
.notebook-preview .notebook-preview table > tbody > tr > td {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.notebook-preview table > tbody > tr + tr > td {
|
||||
border-top: 1px solid;
|
||||
}
|
||||
|
||||
.notebook-preview blockquote {
|
||||
margin: 0 7px 0 5px;
|
||||
padding: 0 16px 0 10px;
|
||||
border-left-width: 5px;
|
||||
border-left-style: solid;
|
||||
}
|
||||
|
||||
.notebook-preview code {
|
||||
font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
|
||||
font-size: 12px;
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
.notebook-preview pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.notebook-preview .mac code {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.notebook-preview pre:not(.hljs),
|
||||
.notebook-preview pre.hljs code > div {
|
||||
padding: 16px;
|
||||
border-radius: 3px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/** Theming */
|
||||
|
||||
.notebook-preview pre code {
|
||||
color: var(--vscode-editor-foreground);
|
||||
}
|
||||
|
||||
|
||||
.notebook-preview pre {
|
||||
background-color: rgba(220, 220, 220, 0.4);
|
||||
}
|
||||
|
||||
.vs-dark .notebook-preview pre {
|
||||
background-color: rgba(10, 10, 10, 0.4);
|
||||
}
|
||||
|
||||
.hc-black .notebook-preview pre {
|
||||
background-color: rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
.hc-black .notebook-preview h1 {
|
||||
border-color: rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
.notebook-preview table > thead > tr > th {
|
||||
border-color: rgba(0, 0, 0, 0.69);
|
||||
}
|
||||
|
||||
.vs-dark .notebook-preview table > thead > tr > th {
|
||||
border-color: rgba(255, 255, 255, 0.69);
|
||||
}
|
||||
|
||||
.notebook-preview h1,
|
||||
.notebook-preview hr,
|
||||
.notebook-preview table > tbody > tr + tr > td {
|
||||
border-color: rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.vs-dark .notebook-preview h1,
|
||||
.vs-dark .notebook-preview hr,
|
||||
.vs-dark .notebook-preview table > tbody > tr + tr > td {
|
||||
border-color: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column" (mouseover)="hover=true" (mouseleave)="hover=false">
|
||||
<div class="notebook-text" style="flex: 0 0 auto;">
|
||||
<code-component *ngIf="isEditMode" [cellModel]="cellModel" (onContentChanged)="handleContentChanged()" [model]="model" [activeCellId]="activeCellId">
|
||||
</code-component>
|
||||
</div>
|
||||
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: row">
|
||||
<div #preview link-handler [isTrusted]="isTrusted" [notebookUri]="notebookUri" class="notebook-preview" style="flex: 1 1 auto" (dblclick)="toggleEditMode()">
|
||||
</div>
|
||||
<div #moreactions class="moreActions" style="flex: 0 0 auto; display: flex; flex-flow:column;width: 20px; min-height: 20px; max-height: 20px; padding-top: 0px; orientation: portrait">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,266 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'vs/css!./textCell';
|
||||
import 'vs/css!./media/markdown';
|
||||
import 'vs/css!./media/highlight';
|
||||
|
||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnChanges, SimpleChange, HostListener } from '@angular/core';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import * as themeColors from 'vs/workbench/common/theme';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IMarkdownRenderResult } from 'vs/editor/contrib/markdown/markdownRenderer';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { NotebookMarkdownRenderer } from 'sql/workbench/parts/notebook/browser/outputs/notebookMarkdown';
|
||||
import { CellView } from 'sql/workbench/parts/notebook/browser/cellViews/interfaces';
|
||||
import { ICellModel } from 'sql/workbench/parts/notebook/browser/models/modelInterfaces';
|
||||
import { NotebookModel } from 'sql/workbench/parts/notebook/browser/models/notebookModel';
|
||||
import { ISanitizer, defaultSanitizer } from 'sql/workbench/parts/notebook/browser/outputs/sanitizer';
|
||||
import { CellToggleMoreActions } from 'sql/workbench/parts/notebook/browser/cellToggleMoreActions';
|
||||
import { CommonServiceInterface } from 'sql/platform/bootstrap/browser/commonServiceInterface.service';
|
||||
import { useInProcMarkdown, convertVscodeResourceToFileInSubDirectories } from 'sql/workbench/parts/notebook/browser/models/notebookUtils';
|
||||
|
||||
export const TEXT_SELECTOR: string = 'text-cell-component';
|
||||
const USER_SELECT_CLASS = 'actionselect';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: TEXT_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('./textCell.component.html'))
|
||||
})
|
||||
export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
||||
@ViewChild('preview', { read: ElementRef }) private output: ElementRef;
|
||||
@ViewChild('moreactions', { read: ElementRef }) private moreActionsElementRef: ElementRef;
|
||||
@Input() cellModel: ICellModel;
|
||||
|
||||
@Input() set model(value: NotebookModel) {
|
||||
this._model = value;
|
||||
}
|
||||
|
||||
@Input() set activeCellId(value: string) {
|
||||
this._activeCellId = value;
|
||||
}
|
||||
|
||||
@Input() set hover(value: boolean) {
|
||||
this._hover = value;
|
||||
if (!this.isActive()) {
|
||||
// Only make a change if we're not active, since this has priority
|
||||
this.updateMoreActions();
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('document:keydown.escape', ['$event'])
|
||||
handleKeyboardEvent() {
|
||||
if (this.isEditMode) {
|
||||
this.toggleEditMode(false);
|
||||
}
|
||||
this.cellModel.active = false;
|
||||
this._model.updateActiveCell(undefined);
|
||||
}
|
||||
|
||||
private _content: string | string[];
|
||||
private _lastTrustedMode: boolean;
|
||||
private isEditMode: boolean;
|
||||
private _sanitizer: ISanitizer;
|
||||
private _model: NotebookModel;
|
||||
private _activeCellId: string;
|
||||
private readonly _onDidClickLink = this._register(new Emitter<URI>());
|
||||
public readonly onDidClickLink = this._onDidClickLink.event;
|
||||
private _cellToggleMoreActions: CellToggleMoreActions;
|
||||
private _hover: boolean;
|
||||
private markdownRenderer: NotebookMarkdownRenderer;
|
||||
private markdownResult: IMarkdownRenderResult;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
|
||||
@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
|
||||
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
|
||||
@Inject(ICommandService) private _commandService: ICommandService,
|
||||
@Inject(IOpenerService) private readonly openerService: IOpenerService,
|
||||
@Inject(IConfigurationService) private configurationService: IConfigurationService,
|
||||
|
||||
) {
|
||||
super();
|
||||
this.isEditMode = true;
|
||||
this._cellToggleMoreActions = this._instantiationService.createInstance(CellToggleMoreActions);
|
||||
this.markdownRenderer = this._instantiationService.createInstance(NotebookMarkdownRenderer);
|
||||
this._register(toDisposable(() => {
|
||||
if (this.markdownResult) {
|
||||
this.markdownResult.dispose();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
//Gets sanitizer from ISanitizer interface
|
||||
private get sanitizer(): ISanitizer {
|
||||
if (this._sanitizer) {
|
||||
return this._sanitizer;
|
||||
}
|
||||
return this._sanitizer = defaultSanitizer;
|
||||
}
|
||||
|
||||
get model(): NotebookModel {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
get activeCellId(): string {
|
||||
return this._activeCellId;
|
||||
}
|
||||
|
||||
private setLoading(isLoading: boolean): void {
|
||||
this.cellModel.loaded = !isLoading;
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
|
||||
this.updateTheme(this.themeService.getColorTheme());
|
||||
this._cellToggleMoreActions.onInit(this.moreActionsElementRef, this.model, this.cellModel);
|
||||
this.setFocusAndScroll();
|
||||
this._register(this.cellModel.onOutputsChanged(e => {
|
||||
this.updatePreview();
|
||||
}));
|
||||
}
|
||||
|
||||
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
|
||||
for (let propName in changes) {
|
||||
if (propName === 'activeCellId') {
|
||||
let changedProp = changes[propName];
|
||||
this._activeCellId = changedProp.currentValue;
|
||||
this.toggleUserSelect(this.isActive());
|
||||
// If the activeCellId is undefined (i.e. in an active cell update), don't unnecessarily set editMode to false;
|
||||
// it will be set to true in a subsequent call to toggleEditMode()
|
||||
if (changedProp.previousValue !== undefined) {
|
||||
this.toggleEditMode(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get isTrusted(): boolean {
|
||||
return this.model.trustedMode;
|
||||
}
|
||||
|
||||
public get notebookUri(): URI {
|
||||
return this.model.notebookUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the preview of markdown component with latest changes
|
||||
* If content is empty and in non-edit mode, default it to 'Double-click to edit'
|
||||
* Sanitizes the data to be shown in markdown cell
|
||||
*/
|
||||
private updatePreview(): void {
|
||||
let trustedChanged = this.cellModel && this._lastTrustedMode !== this.cellModel.trustedMode;
|
||||
let cellModelSourceJoined = Array.isArray(this.cellModel.source) ? this.cellModel.source.join('') : this.cellModel.source;
|
||||
let contentJoined = Array.isArray(this._content) ? this._content.join('') : this._content;
|
||||
let contentChanged = contentJoined !== cellModelSourceJoined || cellModelSourceJoined.length === 0;
|
||||
if (trustedChanged || contentChanged) {
|
||||
this._lastTrustedMode = this.cellModel.trustedMode;
|
||||
if ((!cellModelSourceJoined) && !this.isEditMode) {
|
||||
this._content = localize('doubleClickEdit', "Double-click to edit");
|
||||
} else {
|
||||
this._content = this.cellModel.source;
|
||||
}
|
||||
|
||||
if (useInProcMarkdown(this.configurationService)) {
|
||||
this.markdownRenderer.setNotebookURI(this.cellModel.notebookModel.notebookUri);
|
||||
this.markdownResult = this.markdownRenderer.render({
|
||||
isTrusted: true,
|
||||
value: Array.isArray(this._content) ? this._content.join('') : this._content
|
||||
});
|
||||
this.markdownResult.element.innerHTML = this.sanitizeContent(this.markdownResult.element.innerHTML);
|
||||
this.setLoading(false);
|
||||
let outputElement = <HTMLElement>this.output.nativeElement;
|
||||
outputElement.innerHTML = this.markdownResult.element.innerHTML;
|
||||
} else {
|
||||
this._commandService.executeCommand<string>('notebook.showPreview', this.cellModel.notebookModel.notebookUri, this._content).then((htmlcontent) => {
|
||||
htmlcontent = convertVscodeResourceToFileInSubDirectories(htmlcontent, this.cellModel);
|
||||
htmlcontent = this.sanitizeContent(htmlcontent);
|
||||
let outputElement = <HTMLElement>this.output.nativeElement;
|
||||
outputElement.innerHTML = htmlcontent;
|
||||
this.setLoading(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Sanitizes the content based on trusted mode of Cell Model
|
||||
private sanitizeContent(content: string): string {
|
||||
if (this.cellModel && !this.cellModel.trustedMode) {
|
||||
content = this.sanitizer.sanitize(content);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
// Todo: implement layout
|
||||
public layout() {
|
||||
}
|
||||
|
||||
private updateTheme(theme: IColorTheme): void {
|
||||
let outputElement = <HTMLElement>this.output.nativeElement;
|
||||
outputElement.style.borderTopColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
|
||||
|
||||
let moreActionsEl = <HTMLElement>this.moreActionsElementRef.nativeElement;
|
||||
moreActionsEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
|
||||
}
|
||||
|
||||
public handleContentChanged(): void {
|
||||
this.updatePreview();
|
||||
}
|
||||
|
||||
public toggleEditMode(editMode?: boolean): void {
|
||||
this.isEditMode = editMode !== undefined ? editMode : !this.isEditMode;
|
||||
this.updateMoreActions();
|
||||
this.updatePreview();
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
private updateMoreActions(): void {
|
||||
if (!this.isEditMode && (this.isActive() || this._hover)) {
|
||||
this.toggleMoreActionsButton(true);
|
||||
}
|
||||
else {
|
||||
this.toggleMoreActionsButton(false);
|
||||
}
|
||||
}
|
||||
|
||||
private toggleUserSelect(userSelect: boolean): void {
|
||||
if (!this.output) {
|
||||
return;
|
||||
}
|
||||
if (userSelect) {
|
||||
DOM.addClass(this.output.nativeElement, USER_SELECT_CLASS);
|
||||
} else {
|
||||
DOM.removeClass(this.output.nativeElement, USER_SELECT_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
private setFocusAndScroll(): void {
|
||||
this.toggleEditMode(this.isActive());
|
||||
|
||||
if (this.output && this.output.nativeElement) {
|
||||
(<HTMLElement>this.output.nativeElement).scrollTo({ behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
protected isActive() {
|
||||
return this.cellModel && this.cellModel.id === this.activeCellId;
|
||||
}
|
||||
|
||||
protected toggleMoreActionsButton(isActiveOrHovered: boolean) {
|
||||
this._cellToggleMoreActions.toggleVisible(!isActiveOrHovered);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
text-cell-component {
|
||||
display: block;
|
||||
}
|
||||
|
||||
text-cell-component .notebook-preview {
|
||||
user-select: none;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.notebook-preview.actionselect {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
text-cell-component table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
border: none;
|
||||
font-size: 12px;
|
||||
table-layout: auto;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 1em;
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
text-cell-component thead {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
text-cell-component td,
|
||||
text-cell-component th,
|
||||
text-cell-component tr {
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
padding: 0.5em 0.5em;
|
||||
line-height: normal;
|
||||
white-space: normal;
|
||||
max-width: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
text-cell-component th {
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -23,12 +23,11 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer } from 'vs/workbench/common/views';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { NodeContextKey } from 'sql/workbench/parts/dataExplorer/browser/nodeContext';
|
||||
import { MssqlNodeContext } from 'sql/workbench/parts/dataExplorer/browser/mssqlNodeContext';
|
||||
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
|
||||
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { TreeViewItemHandleArg } from 'sql/workbench/common/views';
|
||||
import { ConnectedContext } from 'azdata';
|
||||
import { TreeNodeContextKey } from 'sql/workbench/parts/objectExplorer/common/treeNodeContextKey';
|
||||
@@ -36,6 +35,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { ObjectExplorerActionsContext } from 'sql/workbench/parts/objectExplorer/browser/objectExplorerActions';
|
||||
import { ItemContextKey } from 'sql/workbench/parts/dashboard/browser/widgets/explorer/explorerTreeContext';
|
||||
import { ManageActionContext } from 'sql/workbench/browser/actions';
|
||||
import { MarkdownOutputComponent } from 'sql/workbench/parts/notebook/browser/outputs/markdownOutput.component';
|
||||
import { registerCellComponent } from 'sql/platform/notebooks/common/outputRegistry';
|
||||
import { TextCellComponent } from 'sql/workbench/parts/notebook/browser/cellViews/textCell.component';
|
||||
|
||||
// Model View editor registration
|
||||
const viewModelEditorDescriptor = new EditorDescriptor(
|
||||
@@ -278,3 +280,16 @@ registerComponentType({
|
||||
ctor: PlotlyOutputComponent,
|
||||
selector: PlotlyOutputComponent.SELECTOR
|
||||
});
|
||||
|
||||
/**
|
||||
* A mime renderer component for Markdown.
|
||||
*/
|
||||
registerComponentType({
|
||||
mimeTypes: ['text/markdown'],
|
||||
rank: 60,
|
||||
safe: true,
|
||||
ctor: MarkdownOutputComponent,
|
||||
selector: MarkdownOutputComponent.SELECTOR
|
||||
});
|
||||
|
||||
registerCellComponent(TextCellComponent);
|
||||
@@ -0,0 +1,5 @@
|
||||
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: row">
|
||||
<div class="icon in-progress" *ngIf="loading === true"></div>
|
||||
<div #output link-handler [isTrusted]="isTrusted" [notebookUri]="notebookUri" class="notebook-preview" style="flex: 1 1 auto">
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,154 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!../cellViews/textCell';
|
||||
import 'vs/css!../cellViews/media/markdown';
|
||||
import 'vs/css!../cellViews/media/highlight';
|
||||
|
||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild } from '@angular/core';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { ISanitizer, defaultSanitizer } from 'sql/workbench/parts/notebook/browser/outputs/sanitizer';
|
||||
import { AngularDisposable } from 'sql/base/browser/lifecycle';
|
||||
import { IMimeComponent } from 'sql/workbench/parts/notebook/browser/outputs/mimeRegistry';
|
||||
import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { NotebookMarkdownRenderer } from 'sql/workbench/parts/notebook/browser/outputs/notebookMarkdown';
|
||||
import { MimeModel } from 'sql/workbench/parts/notebook/browser/models/mimemodel';
|
||||
import { ICellModel } from 'sql/workbench/parts/notebook/browser/models/modelInterfaces';
|
||||
import { useInProcMarkdown, convertVscodeResourceToFileInSubDirectories } from 'sql/workbench/parts/notebook/browser/models/notebookUtils';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
@Component({
|
||||
selector: MarkdownOutputComponent.SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('./markdownOutput.component.html'))
|
||||
})
|
||||
export class MarkdownOutputComponent extends AngularDisposable implements IMimeComponent, OnInit {
|
||||
public static readonly SELECTOR: string = 'markdown-output';
|
||||
|
||||
@ViewChild('output', { read: ElementRef }) private output: ElementRef;
|
||||
|
||||
private _sanitizer: ISanitizer;
|
||||
private _lastTrustedMode: boolean;
|
||||
|
||||
private _bundleOptions: MimeModel.IOptions;
|
||||
private _initialized: boolean = false;
|
||||
public loading: boolean = false;
|
||||
private _cellModel: ICellModel;
|
||||
private _markdownRenderer: NotebookMarkdownRenderer;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
|
||||
@Inject(ICommandService) private _commandService: ICommandService,
|
||||
@Inject(INotebookService) private _notebookService: INotebookService,
|
||||
@Inject(IConfigurationService) private _configurationService: IConfigurationService,
|
||||
@Inject(IInstantiationService) private _instantiationService: IInstantiationService
|
||||
|
||||
) {
|
||||
super();
|
||||
this._sanitizer = this._notebookService.getMimeRegistry().sanitizer;
|
||||
this._markdownRenderer = this._instantiationService.createInstance(NotebookMarkdownRenderer);
|
||||
}
|
||||
|
||||
@Input() set bundleOptions(value: MimeModel.IOptions) {
|
||||
this._bundleOptions = value;
|
||||
if (this._initialized) {
|
||||
this.updatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
@Input() mimeType: string;
|
||||
|
||||
get cellModel(): ICellModel {
|
||||
return this._cellModel;
|
||||
}
|
||||
|
||||
@Input() set cellModel(value: ICellModel) {
|
||||
this._cellModel = value;
|
||||
if (this._initialized) {
|
||||
this.updatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
public get isTrusted(): boolean {
|
||||
return this._bundleOptions && this._bundleOptions.trusted;
|
||||
}
|
||||
|
||||
public get notebookUri(): URI {
|
||||
return this.cellModel.notebookModel.notebookUri;
|
||||
}
|
||||
|
||||
//Gets sanitizer from ISanitizer interface
|
||||
private get sanitizer(): ISanitizer {
|
||||
if (this._sanitizer) {
|
||||
return this._sanitizer;
|
||||
}
|
||||
return this._sanitizer = defaultSanitizer;
|
||||
}
|
||||
|
||||
private setLoading(isLoading: boolean): void {
|
||||
this.loading = isLoading;
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.updatePreview();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the preview of markdown component with latest changes
|
||||
* If content is empty and in non-edit mode, default it to 'Double-click to edit'
|
||||
* Sanitizes the data to be shown in markdown cell
|
||||
*/
|
||||
private updatePreview() {
|
||||
if (!this._bundleOptions || !this._cellModel) {
|
||||
return;
|
||||
}
|
||||
let trustedChanged = this._bundleOptions && this._lastTrustedMode !== this.isTrusted;
|
||||
if (trustedChanged || !this._initialized) {
|
||||
this._lastTrustedMode = this.isTrusted;
|
||||
let content = this._bundleOptions.data['text/markdown'];
|
||||
if (useInProcMarkdown(this._configurationService)) {
|
||||
this._markdownRenderer.setNotebookURI(this.cellModel.notebookModel.notebookUri);
|
||||
let markdownResult = this._markdownRenderer.render({
|
||||
isTrusted: this.cellModel.trustedMode,
|
||||
value: content.toString()
|
||||
});
|
||||
let outputElement = <HTMLElement>this.output.nativeElement;
|
||||
outputElement.innerHTML = markdownResult.element.innerHTML;
|
||||
} else {
|
||||
if (!content) {
|
||||
|
||||
} else {
|
||||
this._commandService.executeCommand<string>('notebook.showPreview', this._cellModel.notebookModel.notebookUri, content).then((htmlcontent) => {
|
||||
htmlcontent = convertVscodeResourceToFileInSubDirectories(htmlcontent, this._cellModel);
|
||||
htmlcontent = this.sanitizeContent(htmlcontent);
|
||||
let outputElement = <HTMLElement>this.output.nativeElement;
|
||||
outputElement.innerHTML = htmlcontent;
|
||||
this.setLoading(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
this._initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
//Sanitizes the content based on trusted mode of Cell Model
|
||||
private sanitizeContent(content: string): string {
|
||||
if (this.isTrusted) {
|
||||
content = this.sanitizer.sanitize(content);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
public layout() {
|
||||
// Do we need to update on layout changed?
|
||||
}
|
||||
|
||||
public handleContentChanged(): void {
|
||||
this.updatePreview();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as path from 'vs/base/common/path';
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
import { IMarkdownString, removeMarkdownEscapes } from 'vs/base/common/htmlContent';
|
||||
import { IMarkdownRenderResult } from 'vs/editor/contrib/markdown/markdownRenderer';
|
||||
import * as marked from 'vs/base/common/marked/marked';
|
||||
import { defaultGenerator } from 'vs/base/common/idGenerator';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer';
|
||||
|
||||
// Based off of HtmlContentRenderer
|
||||
export class NotebookMarkdownRenderer {
|
||||
private _notebookURI: URI;
|
||||
private _baseUrls: string[] = [];
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
render(markdown: IMarkdownString): IMarkdownRenderResult {
|
||||
const element: HTMLElement = markdown ? this.renderMarkdown(markdown, undefined) : document.createElement('span');
|
||||
return {
|
||||
element,
|
||||
dispose: () => { }
|
||||
};
|
||||
}
|
||||
|
||||
createElement(options: MarkdownRenderOptions): HTMLElement {
|
||||
const tagName = options.inline ? 'span' : 'div';
|
||||
const element = document.createElement(tagName);
|
||||
if (options.className) {
|
||||
element.className = options.className;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
parse(text: string): any {
|
||||
let data = JSON.parse(text);
|
||||
data = revive(data, 0);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create html nodes for the given content element.
|
||||
* Adapted from htmlContentRenderer. Ensures that the markdown renderer
|
||||
* gets passed in the correct baseUrl for the notebook's saved location,
|
||||
* respects the trusted state of a notebook, and allows command links to
|
||||
* be clickable.
|
||||
*/
|
||||
renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}): HTMLElement {
|
||||
const element = this.createElement(options);
|
||||
|
||||
// signal to code-block render that the element has been created
|
||||
let signalInnerHTML: () => void;
|
||||
const withInnerHTML = new Promise(c => signalInnerHTML = c);
|
||||
|
||||
let notebookFolder = path.dirname(this._notebookURI.fsPath) + '/';
|
||||
if (!this._baseUrls.includes(notebookFolder)) {
|
||||
this._baseUrls.push(notebookFolder);
|
||||
}
|
||||
const renderer = new marked.Renderer({ baseUrl: notebookFolder });
|
||||
renderer.image = (href: string, title: string, text: string) => {
|
||||
href = this.cleanUrl(!markdown.isTrusted, notebookFolder, href);
|
||||
let dimensions: string[] = [];
|
||||
if (href) {
|
||||
const splitted = href.split('|').map(s => s.trim());
|
||||
href = splitted[0];
|
||||
const parameters = splitted[1];
|
||||
if (parameters) {
|
||||
const heightFromParams = /height=(\d+)/.exec(parameters);
|
||||
const widthFromParams = /width=(\d+)/.exec(parameters);
|
||||
const height = heightFromParams ? heightFromParams[1] : '';
|
||||
const width = widthFromParams ? widthFromParams[1] : '';
|
||||
const widthIsFinite = isFinite(parseInt(width));
|
||||
const heightIsFinite = isFinite(parseInt(height));
|
||||
if (widthIsFinite) {
|
||||
dimensions.push(`width="${width}"`);
|
||||
}
|
||||
if (heightIsFinite) {
|
||||
dimensions.push(`height="${height}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
let attributes: string[] = [];
|
||||
if (href) {
|
||||
attributes.push(`src="${href}"`);
|
||||
}
|
||||
if (text) {
|
||||
attributes.push(`alt="${text}"`);
|
||||
}
|
||||
if (title) {
|
||||
attributes.push(`title="${title}"`);
|
||||
}
|
||||
if (dimensions.length) {
|
||||
attributes = attributes.concat(dimensions);
|
||||
}
|
||||
return '<img ' + attributes.join(' ') + '>';
|
||||
};
|
||||
renderer.link = (href: string, title: string, text: string): string => {
|
||||
href = this.cleanUrl(!markdown.isTrusted, notebookFolder, href);
|
||||
if (href === null) {
|
||||
return text;
|
||||
}
|
||||
// Remove markdown escapes. Workaround for https://github.com/chjj/marked/issues/829
|
||||
if (href === text) { // raw link case
|
||||
text = removeMarkdownEscapes(text);
|
||||
}
|
||||
title = removeMarkdownEscapes(title);
|
||||
href = removeMarkdownEscapes(href);
|
||||
if (
|
||||
!href
|
||||
|| !markdown.isTrusted
|
||||
|| href.match(/^data:|javascript:/i)
|
||||
|| href.match(/^command:(\/\/\/)?_workbench\.downloadResource/i)
|
||||
) {
|
||||
// drop the link
|
||||
return text;
|
||||
|
||||
} else {
|
||||
// HTML Encode href
|
||||
href = href.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
return `<a href=${href} data-href="${href}" title="${title || href}">${text}</a>`;
|
||||
}
|
||||
};
|
||||
renderer.paragraph = (text): string => {
|
||||
return `<p>${text}</p>`;
|
||||
};
|
||||
|
||||
if (options.codeBlockRenderer) {
|
||||
renderer.code = (code, lang) => {
|
||||
const value = options.codeBlockRenderer!(lang, code);
|
||||
// when code-block rendering is async we return sync
|
||||
// but update the node with the real result later.
|
||||
const id = defaultGenerator.nextId();
|
||||
|
||||
const promise = value.then(strValue => {
|
||||
withInnerHTML.then(e => {
|
||||
const span = element.querySelector(`div[data-code="${id}"]`);
|
||||
if (span) {
|
||||
span.innerHTML = strValue;
|
||||
}
|
||||
}).catch(err => {
|
||||
// ignore
|
||||
});
|
||||
});
|
||||
|
||||
if (options.codeBlockRenderCallback) {
|
||||
promise.then(options.codeBlockRenderCallback);
|
||||
}
|
||||
|
||||
return `<div class="code" data-code="${id}">${escape(code)}</div>`;
|
||||
};
|
||||
}
|
||||
|
||||
const markedOptions: marked.MarkedOptions = {
|
||||
sanitize: !markdown.isTrusted,
|
||||
renderer,
|
||||
baseUrl: notebookFolder
|
||||
};
|
||||
|
||||
element.innerHTML = marked.parse(markdown.value, markedOptions);
|
||||
signalInnerHTML!();
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
// This following methods have been adapted from marked.js
|
||||
// Copyright (c) 2011-2014, Christopher Jeffrey (https://github.com/chjj/)
|
||||
cleanUrl(sanitize: boolean, base: string, href: string) {
|
||||
if (sanitize) {
|
||||
let prot: string;
|
||||
try {
|
||||
prot = decodeURIComponent(unescape(href))
|
||||
.replace(/[^\w:]/g, '')
|
||||
.toLowerCase();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (URI.parse(href)) {
|
||||
return href;
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
let originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
|
||||
if (base && !originIndependentUrl.test(href) && !path.isAbsolute(href)) {
|
||||
href = this.resolveUrl(base, href);
|
||||
}
|
||||
try {
|
||||
href = encodeURI(href).replace(/%25/g, '%');
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
return href;
|
||||
|
||||
}
|
||||
|
||||
resolveUrl(base: string, href: string) {
|
||||
if (!this._baseUrls[' ' + base]) {
|
||||
// we can ignore everything in base after the last slash of its path component,
|
||||
// but we might need to add _that_
|
||||
// https://tools.ietf.org/html/rfc3986#section-3
|
||||
if (/^[^:]+:\/*[^/]*$/.test(base)) {
|
||||
this._baseUrls[' ' + base] = base + '/';
|
||||
} else {
|
||||
// Remove trailing 'c's. /c*$/ is vulnerable to REDOS.
|
||||
this._baseUrls[' ' + base] = base.replace(/c*$/, '');
|
||||
}
|
||||
}
|
||||
base = this._baseUrls[' ' + base];
|
||||
|
||||
if (href.slice(0, 2) === '//') {
|
||||
return base.replace(/:[\s\S]*/, ':') + href;
|
||||
} else if (href.charAt(0) === '/') {
|
||||
return base.replace(/(:\/*[^/]*)[\s\S]*/, '$1') + href;
|
||||
} else if (href.slice(0, 2) === '..') {
|
||||
return path.join(base, href);
|
||||
} else {
|
||||
return base + href;
|
||||
}
|
||||
}
|
||||
|
||||
// end marked.js adaptation
|
||||
|
||||
setNotebookURI(val: URI) {
|
||||
this._notebookURI = val;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user