mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
* Fix #5238 Notebooks should support relative links - Added detection of relative #links inside notebooks - Added handling of these, at least for current notebook Not handled: open other notebook & scroll to position.
This commit is contained in:
@@ -5,19 +5,27 @@
|
|||||||
|
|
||||||
import { Directive, Inject, HostListener, Input } from '@angular/core';
|
import { Directive, Inject, HostListener, Input } from '@angular/core';
|
||||||
|
|
||||||
|
import * as types from 'vs/base/common/types';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||||
import product from 'vs/platform/product/node/product';
|
import product from 'vs/platform/product/node/product';
|
||||||
|
import { INotebookService } from 'sql/workbench/services/notebook/common/notebookService';
|
||||||
|
|
||||||
const knownSchemes = new Set(['http', 'https', 'file', 'mailto', 'data', `${product.urlProtocol}`, 'azuredatastudio', 'azuredatastudio-insiders', 'vscode', 'vscode-insiders', 'vscode-resource']);
|
const knownSchemes = new Set(['http', 'https', 'file', 'mailto', 'data', `${product.urlProtocol}`, 'azuredatastudio', 'azuredatastudio-insiders', 'vscode', 'vscode-insiders', 'vscode-resource']);
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[link-handler]',
|
selector: '[link-handler]',
|
||||||
})
|
})
|
||||||
export class LinkHandlerDirective {
|
export class LinkHandlerDirective {
|
||||||
|
private workbenchFilePath: URI;
|
||||||
@Input() isTrusted: boolean;
|
@Input() isTrusted: boolean;
|
||||||
constructor(@Inject(IOpenerService) private readonly openerService: IOpenerService) {
|
@Input() notebookUri: URI;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(IOpenerService) private readonly openerService: IOpenerService,
|
||||||
|
@Inject(INotebookService) private readonly notebookService: INotebookService
|
||||||
|
) {
|
||||||
|
this.workbenchFilePath = URI.parse(require.toUrl('vs/code/electron-browser/workbench/workbench.html'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('click', ['$event'])
|
@HostListener('click', ['$event'])
|
||||||
@@ -52,7 +60,11 @@ export class LinkHandlerDirective {
|
|||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
if (uri && this.openerService && this.isSupportedLink(uri)) {
|
if (uri && this.openerService && this.isSupportedLink(uri)) {
|
||||||
this.openerService.open(uri).catch(onUnexpectedError);
|
if (uri.fragment && uri.fragment.length > 0 && uri.path === this.workbenchFilePath.path) {
|
||||||
|
this.notebookService.navigateTo(this.notebookUri, uri.fragment);
|
||||||
|
} else {
|
||||||
|
this.openerService.open(uri).catch(onUnexpectedError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
-->
|
-->
|
||||||
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
|
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
|
||||||
<div #outputarea link-handler [isTrusted]="isTrusted" class="notebook-output" style="flex: 0 0 auto;">
|
<div #outputarea link-handler [isTrusted]="isTrusted" [notebookUri]="notebookUri" class="notebook-output" style="flex: 0 0 auto;">
|
||||||
<output-component *ngFor="let output of cellModel.outputs" [cellOutput]="output" [trustedMode] = "cellModel.trustedMode" [cellModel]="cellModel" [activeCellId]="activeCellId">
|
<output-component *ngFor="let output of cellModel.outputs" [cellOutput]="output" [trustedMode] = "cellModel.trustedMode" [cellModel]="cellModel" [activeCellId]="activeCellId">
|
||||||
</output-component>
|
</output-component>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { AngularDisposable } from 'sql/base/node/lifecycle';
|
|||||||
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||||
import * as themeColors from 'vs/workbench/common/theme';
|
import * as themeColors from 'vs/workbench/common/theme';
|
||||||
import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
|
||||||
export const OUTPUT_AREA_SELECTOR: string = 'output-area-component';
|
export const OUTPUT_AREA_SELECTOR: string = 'output-area-component';
|
||||||
|
|
||||||
@@ -48,6 +49,10 @@ export class OutputAreaComponent extends AngularDisposable implements OnInit {
|
|||||||
return this.cellModel.trustedMode;
|
return this.cellModel.trustedMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get notebookUri(): URI {
|
||||||
|
return this.cellModel.notebookModel.notebookUri;
|
||||||
|
}
|
||||||
|
|
||||||
private setFocusAndScroll(node: HTMLElement): void {
|
private setFocusAndScroll(node: HTMLElement): void {
|
||||||
if (node) {
|
if (node) {
|
||||||
node.focus();
|
node.focus();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
</code-component>
|
</code-component>
|
||||||
</div>
|
</div>
|
||||||
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: row">
|
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: row">
|
||||||
<div #preview link-handler [isTrusted]="isTrusted" class="notebook-preview" style="flex: 1 1 auto" (dblclick)="toggleEditMode()">
|
<div #preview link-handler [isTrusted]="isTrusted" [notebookUri]="notebookUri" class="notebook-preview" style="flex: 1 1 auto" (dblclick)="toggleEditMode()">
|
||||||
</div>
|
</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 #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>
|
||||||
|
|||||||
@@ -154,6 +154,10 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
return this.model.trustedMode;
|
return this.model.trustedMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get notebookUri(): URI {
|
||||||
|
return this.model.notebookUri;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the preview of markdown component with latest changes
|
* 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'
|
* If content is empty and in non-edit mode, default it to 'Double-click to edit'
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { AngularDisposable } from 'sql/base/node/lifecycle';
|
|||||||
import { CellTypes, CellType } from 'sql/workbench/parts/notebook/models/contracts';
|
import { CellTypes, CellType } from 'sql/workbench/parts/notebook/models/contracts';
|
||||||
import { ICellModel, IModelFactory, INotebookModel, NotebookContentChange } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
import { ICellModel, IModelFactory, INotebookModel, NotebookContentChange } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||||
import { INotebookService, INotebookParams, INotebookManager, INotebookEditor, DEFAULT_NOTEBOOK_PROVIDER, SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService';
|
import { INotebookService, INotebookParams, INotebookManager, INotebookEditor, INotebookSection, DEFAULT_NOTEBOOK_PROVIDER, SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService';
|
||||||
import { IBootstrapParams } from 'sql/platform/bootstrap/node/bootstrapService';
|
import { IBootstrapParams } from 'sql/platform/bootstrap/node/bootstrapService';
|
||||||
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||||
import { ModelFactory } from 'sql/workbench/parts/notebook/models/modelFactory';
|
import { ModelFactory } from 'sql/workbench/parts/notebook/models/modelFactory';
|
||||||
@@ -582,4 +582,48 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSections(): INotebookSection[] {
|
||||||
|
return this.getSectionElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSectionElements(): NotebookSection[] {
|
||||||
|
let headers: NotebookSection[] = [];
|
||||||
|
let el: HTMLElement = this.container.nativeElement;
|
||||||
|
let headerElements = el.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
||||||
|
for (let i = 0; i < headerElements.length; i++) {
|
||||||
|
let headerEl = headerElements[i] as HTMLElement;
|
||||||
|
if (headerEl['id']) {
|
||||||
|
headers.push(new NotebookSection(headerEl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateToSection(id: string): void {
|
||||||
|
id = id.toLowerCase();
|
||||||
|
let section = this.getSectionElements().find(s => s.relativeUri && s.relativeUri.toLowerCase() === id);
|
||||||
|
if (section) {
|
||||||
|
// Scroll this section to the top of the header instead of just bringing header into view.
|
||||||
|
let scrollTop = jQuery(section.headerEl).offset().top;
|
||||||
|
(<HTMLElement>this.container.nativeElement).scrollTo({
|
||||||
|
top: scrollTop,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
section.headerEl.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NotebookSection implements INotebookSection {
|
||||||
|
|
||||||
|
constructor(public headerEl: HTMLElement) {
|
||||||
|
}
|
||||||
|
|
||||||
|
get relativeUri(): string {
|
||||||
|
return this.headerEl['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
get header(): string {
|
||||||
|
return this.headerEl.textContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: row">
|
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: row">
|
||||||
<div class="icon in-progress" *ngIf="loading === true"></div>
|
<div class="icon in-progress" *ngIf="loading === true"></div>
|
||||||
<div #output link-handler [isTrusted]="isTrusted" class="notebook-preview" style="flex: 1 1 auto">
|
<div #output link-handler [isTrusted]="isTrusted" [notebookUri]="notebookUri" class="notebook-preview" style="flex: 1 1 auto">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { convertVscodeResourceToFileInSubDirectories, useInProcMarkdown } from '
|
|||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { NotebookMarkdownRenderer } from 'sql/workbench/parts/notebook/outputs/notebookMarkdown';
|
import { NotebookMarkdownRenderer } from 'sql/workbench/parts/notebook/outputs/notebookMarkdown';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: MarkdownOutputComponent.SELECTOR,
|
selector: MarkdownOutputComponent.SELECTOR,
|
||||||
@@ -75,6 +76,10 @@ export class MarkdownOutputComponent extends AngularDisposable implements IMimeC
|
|||||||
return this._bundleOptions && this._bundleOptions.trusted;
|
return this._bundleOptions && this._bundleOptions.trusted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get notebookUri(): URI {
|
||||||
|
return this.cellModel.notebookModel.notebookUri;
|
||||||
|
}
|
||||||
|
|
||||||
//Gets sanitizer from ISanitizer interface
|
//Gets sanitizer from ISanitizer interface
|
||||||
private get sanitizer(): ISanitizer {
|
private get sanitizer(): ISanitizer {
|
||||||
if (this._sanitizer) {
|
if (this._sanitizer) {
|
||||||
|
|||||||
@@ -90,6 +90,12 @@ export interface INotebookService {
|
|||||||
*/
|
*/
|
||||||
serializeNotebookStateChange(notebookUri: URI, changeType: NotebookChangeType): void;
|
serializeNotebookStateChange(notebookUri: URI, changeType: NotebookChangeType): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param notebookUri URI of the notebook to navigate to
|
||||||
|
* @param sectionId ID of the section to navigate to
|
||||||
|
*/
|
||||||
|
navigateTo(notebookUri: URI, sectionId: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INotebookProvider {
|
export interface INotebookProvider {
|
||||||
@@ -117,6 +123,15 @@ export interface INotebookParams extends IBootstrapParams {
|
|||||||
modelFactory?: ModelFactory;
|
modelFactory?: ModelFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a section in a notebook as the header text for that section,
|
||||||
|
* the relative URI that can be used to link to it inside Notebook documents
|
||||||
|
*/
|
||||||
|
export interface INotebookSection {
|
||||||
|
header: string;
|
||||||
|
relativeUri: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface INotebookEditor {
|
export interface INotebookEditor {
|
||||||
readonly notebookParams: INotebookParams;
|
readonly notebookParams: INotebookParams;
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
@@ -131,4 +146,6 @@ export interface INotebookEditor {
|
|||||||
runAllCells(startCell?: ICellModel, endCell?: ICellModel): Promise<boolean>;
|
runAllCells(startCell?: ICellModel, endCell?: ICellModel): Promise<boolean>;
|
||||||
clearOutput(cell: ICellModel): Promise<boolean>;
|
clearOutput(cell: ICellModel): Promise<boolean>;
|
||||||
clearAllOutputs(): Promise<boolean>;
|
clearAllOutputs(): Promise<boolean>;
|
||||||
|
getSections(): INotebookSection[];
|
||||||
|
navigateToSection(sectionId: string): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -619,4 +619,11 @@ export class NotebookService extends Disposable implements INotebookService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
navigateTo(notebookUri: URI, sectionId: string): void {
|
||||||
|
let editor = this._editors.get(notebookUri.toString());
|
||||||
|
if (editor) {
|
||||||
|
editor.navigateToSection(sectionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user