mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-29 09:35:38 -05:00
Callout Dialog Fixes + WYSIWYG Improvements for Insert Link (#14494)
* wip * Works in all edit modes * Default value set * wip * preventdefault * cleanup, add tests * markup -> markdown * Ensure selection is persisted for WYSIWYG * Add simple dialog tests and some PR feedback * floating promise * PR comments, formatted markdown refactor * Change escaping logic + PR comments * PR feedback
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
|
||||
/**
|
||||
* Escape string to be used as label in markdown link
|
||||
* @param unescapedLabel label to escape
|
||||
*/
|
||||
export function escapeLabel(unescapedLabel: string): string {
|
||||
let firstEscape = strings.escape(unescapedLabel);
|
||||
return firstEscape.replace(/[[]]/g, function (match) {
|
||||
switch (match) {
|
||||
case '[': return '\[';
|
||||
case ']': return '\]';
|
||||
default: return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape string to be used as url in markdown link
|
||||
* @param unescapedUrl url to escapes
|
||||
*/
|
||||
export function escapeUrl(unescapedUrl: string): string {
|
||||
let firstEscape = strings.escape(unescapedUrl);
|
||||
return firstEscape.replace(/[()]/g, function (match) {
|
||||
switch (match) {
|
||||
case '(': return '%28';
|
||||
case ')': return '%29';
|
||||
default: return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import 'vs/css!./media/imageCalloutDialog';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as styler from 'vs/platform/theme/common/styler';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as constants from 'sql/workbench/contrib/notebook/browser/calloutDialog/common/constants';
|
||||
@@ -27,10 +26,11 @@ import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox';
|
||||
import { RadioButton } from 'sql/base/browser/ui/radioButton/radioButton';
|
||||
import { DialogWidth } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { attachModalDialogStyler } from 'sql/workbench/common/styler';
|
||||
import { escapeUrl } from 'sql/workbench/contrib/notebook/browser/calloutDialog/common/utils';
|
||||
|
||||
export interface IImageCalloutDialogOptions {
|
||||
insertTitle?: string,
|
||||
insertMarkup?: string,
|
||||
insertEscapedMarkdown?: string,
|
||||
imagePath?: string,
|
||||
embedImage?: boolean
|
||||
}
|
||||
@@ -186,7 +186,7 @@ export class ImageCalloutDialog extends CalloutDialog<IImageCalloutDialogOptions
|
||||
public insert(): void {
|
||||
this.hide();
|
||||
this._selectionComplete.resolve({
|
||||
insertMarkup: `<img src="${strings.escape(this._imageUrlInputBox.value)}">`,
|
||||
insertEscapedMarkdown: `})`,
|
||||
imagePath: this._imageUrlInputBox.value,
|
||||
embedImage: this._imageEmbedCheckbox.checked
|
||||
});
|
||||
@@ -196,7 +196,7 @@ export class ImageCalloutDialog extends CalloutDialog<IImageCalloutDialogOptions
|
||||
public cancel(): void {
|
||||
super.cancel();
|
||||
this._selectionComplete.resolve({
|
||||
insertMarkup: '',
|
||||
insertEscapedMarkdown: '',
|
||||
imagePath: undefined,
|
||||
embedImage: undefined
|
||||
});
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import 'vs/css!./media/linkCalloutDialog';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as styler from 'vs/platform/theme/common/styler';
|
||||
import * as constants from 'sql/workbench/contrib/notebook/browser/calloutDialog/common/constants';
|
||||
import { CalloutDialog } from 'sql/workbench/browser/modal/calloutDialog';
|
||||
@@ -20,12 +19,17 @@ import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||
import { DialogWidth } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { attachModalDialogStyler } from 'sql/workbench/common/styler';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { escapeLabel, escapeUrl } from 'sql/workbench/contrib/notebook/browser/calloutDialog/common/utils';
|
||||
|
||||
const DEFAULT_DIALOG_WIDTH = 452;
|
||||
|
||||
export interface ILinkCalloutDialogOptions {
|
||||
insertTitle?: string,
|
||||
insertMarkup?: string
|
||||
insertEscapedMarkdown?: string,
|
||||
insertUnescapedLinkLabel?: string,
|
||||
insertUnescapedLinkUrl?: string
|
||||
}
|
||||
|
||||
export class LinkCalloutDialog extends CalloutDialog<ILinkCalloutDialogOptions> {
|
||||
@@ -34,11 +38,12 @@ export class LinkCalloutDialog extends CalloutDialog<ILinkCalloutDialogOptions>
|
||||
private _linkTextInputBox: InputBox;
|
||||
private _linkAddressLabel: HTMLElement;
|
||||
private _linkUrlInputBox: InputBox;
|
||||
private _previouslySelectedRange: Range;
|
||||
|
||||
constructor(
|
||||
title: string,
|
||||
width: DialogWidth,
|
||||
dialogProperties: IDialogProperties,
|
||||
private readonly _defaultLabel: string = '',
|
||||
@IContextViewService private readonly _contextViewService: IContextViewService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@ILayoutService layoutService: ILayoutService,
|
||||
@@ -50,7 +55,7 @@ export class LinkCalloutDialog extends CalloutDialog<ILinkCalloutDialogOptions>
|
||||
) {
|
||||
super(
|
||||
title,
|
||||
width,
|
||||
DEFAULT_DIALOG_WIDTH,
|
||||
dialogProperties,
|
||||
themeService,
|
||||
layoutService,
|
||||
@@ -60,12 +65,17 @@ export class LinkCalloutDialog extends CalloutDialog<ILinkCalloutDialogOptions>
|
||||
logService,
|
||||
textResourcePropertiesService
|
||||
);
|
||||
let selection = window.getSelection();
|
||||
if (selection.rangeCount > 0) {
|
||||
this._previouslySelectedRange = selection?.getRangeAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the dialog and returns a promise for what options the user chooses.
|
||||
*/
|
||||
public open(): Promise<ILinkCalloutDialogOptions> {
|
||||
this._selectionComplete = new Deferred<ILinkCalloutDialogOptions>();
|
||||
this.show();
|
||||
return this._selectionComplete.promise;
|
||||
}
|
||||
@@ -97,6 +107,7 @@ export class LinkCalloutDialog extends CalloutDialog<ILinkCalloutDialogOptions>
|
||||
placeholder: constants.linkTextPlaceholder,
|
||||
ariaLabel: constants.linkTextLabel
|
||||
});
|
||||
this._linkTextInputBox.value = this._defaultLabel;
|
||||
DOM.append(linkTextRow, linkTextInputContainer);
|
||||
|
||||
let linkAddressRow = DOM.$('.row');
|
||||
@@ -121,18 +132,45 @@ export class LinkCalloutDialog extends CalloutDialog<ILinkCalloutDialogOptions>
|
||||
this._register(styler.attachInputBoxStyler(this._linkUrlInputBox, this._themeService));
|
||||
}
|
||||
|
||||
protected onAccept(e?: StandardKeyboardEvent) {
|
||||
// EventHelper.stop() will call preventDefault. Without it, text cell will insert an extra newline when pressing enter on dialog
|
||||
DOM.EventHelper.stop(e, true);
|
||||
this.insert();
|
||||
}
|
||||
|
||||
protected onClose(e?: StandardKeyboardEvent) {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
public insert(): void {
|
||||
this.hide();
|
||||
this._selectionComplete.resolve({
|
||||
insertMarkup: `<a href="${strings.escape(this._linkUrlInputBox.value)}">${strings.escape(this._linkTextInputBox.value)}</a>`,
|
||||
});
|
||||
this.dispose();
|
||||
let escapedLabel = escapeLabel(this._linkTextInputBox.value);
|
||||
let escapedUrl = escapeUrl(this._linkUrlInputBox.value);
|
||||
|
||||
if (this._previouslySelectedRange) {
|
||||
// Reset selection to previous state before callout was open
|
||||
let selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(this._previouslySelectedRange);
|
||||
|
||||
this._selectionComplete.resolve({
|
||||
insertEscapedMarkdown: `[${escapedLabel}](${escapedUrl})`,
|
||||
insertUnescapedLinkLabel: this._linkTextInputBox.value,
|
||||
insertUnescapedLinkUrl: this._linkUrlInputBox.value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
super.cancel();
|
||||
this._selectionComplete.resolve({
|
||||
insertMarkup: ''
|
||||
insertEscapedMarkdown: '',
|
||||
insertUnescapedLinkLabel: escapeLabel(this._linkTextInputBox.value)
|
||||
});
|
||||
}
|
||||
|
||||
public set url(val: string) {
|
||||
this._linkUrlInputBox.value = val;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user