Skip to content

Commit 016a2ab

Browse files
committed
Fix error in keydown
1 parent edba4e7 commit 016a2ab

File tree

1 file changed

+83
-97
lines changed

1 file changed

+83
-97
lines changed

src/CodeSnippetInputDialog.ts

Lines changed: 83 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
// Copyright (c) 2020, jupytercalpoly
22
// Distributed under the terms of the BSD-3 Clause License.
33

4-
import { showDialog, Dialog } from '@jupyterlab/apputils';
4+
import { Dialog } from '@jupyterlab/apputils';
55
import { addIcon, checkIcon } from '@jupyterlab/ui-components';
66
import { Contents } from '@jupyterlab/services';
77

88
import { Widget } from '@lumino/widgets';
9-
import { JSONObject } from '@lumino/coreutils';
9+
import { Message } from '@lumino/messaging';
1010

1111
import { ICodeSnippet, CodeSnippetService } from './CodeSnippetService';
12+
import { showMessage } from './CodeSnippetConfirmMessage';
1213

1314
import { CodeSnippetWidget } from './CodeSnippetWidget';
1415
import { SUPPORTED_LANGUAGES } from './CodeSnippetLanguages';
15-
import { showMessage } from './ConfirmMessage';
16-
import { validateInputs } from './CodeSnippetUtilities';
17-
18-
import checkSVGstr from '../style/icon/jupyter_checkmark.svg';
16+
import { validateInputs, saveOverWriteFile } from './CodeSnippetUtilities';
1917

2018
/**
2119
* The class name added to file dialogs.
@@ -25,25 +23,72 @@ const FILE_DIALOG_CLASS = 'jp-codeSnippet-fileDialog';
2523
/**
2624
* CSS STYLING
2725
*/
28-
const CODE_SNIPPET_DIALOG_INPUT = 'jp-codeSnippet-dialog-input';
26+
// const CODE_SNIPPET_DIALOG_INPUT = 'jp-codeSnippet-dialog-input';
27+
const CODE_SNIPPET_DIALOG_NAME_INPUT = 'jp-codeSnippet-dialog-name-input';
28+
const CODE_SNIPPET_DIALOG_DESC_INPUT = 'jp-codeSnippet-dialog-desc-input';
29+
const CODE_SNIPPET_DIALOG_LANG_INPUT = 'jp-codeSnippet-dialog-lang-input';
2930
const CODE_SNIPPET_INPUTTAG_PLUS_ICON = 'jp-codeSnippet-inputTag-plusIcon';
3031
const CODE_SNIPPET_INPUTTAG_LIST = 'jp-codeSnippet-inputTagList';
3132
const CODE_SNIPPET_INPUT_TAG = 'jp-codeSnippet-inputTag';
3233
const CODE_SNIPPET_INPUT_TAG_CHECK = 'jp-codeSnippet-inputTag-check';
33-
const CODE_SNIPPET_CONFIRM_TEXT = 'jp-codeSnippet-confirm-text';
3434

35-
/**
36-
* A stripped-down interface for a file container.
37-
*/
38-
export interface IFileContainer extends JSONObject {
39-
/**
40-
* The list of item names in the current working directory.
41-
*/
42-
items: string[];
43-
/**
44-
* The current working directory of the file container.
45-
*/
46-
path: string;
35+
class CodeSnippetDialog extends Dialog<any> {
36+
first: HTMLElement;
37+
protected onAfterAttach(msg: Message): void {
38+
const node = this.node;
39+
node.addEventListener('keydown', this, false);
40+
node.addEventListener('contextmenu', this, true);
41+
node.addEventListener('click', this, true);
42+
document.addEventListener('focus', this, true);
43+
44+
const body = this.node.querySelector('.jp-Dialog-body');
45+
const el = body.querySelector(
46+
`.${CODE_SNIPPET_DIALOG_NAME_INPUT}`
47+
) as HTMLElement;
48+
this.first = el;
49+
el.focus();
50+
}
51+
52+
protected onAfterDetach(msg: Message): void {
53+
const node = this.node;
54+
node.removeEventListener('keydown', this, false);
55+
node.removeEventListener('contextmenu', this, true);
56+
node.removeEventListener('click', this, true);
57+
document.removeEventListener('focus', this, true);
58+
}
59+
60+
protected _evtKeydown(event: KeyboardEvent): void {
61+
switch (event.key) {
62+
case 'Escape':
63+
event.stopPropagation();
64+
event.preventDefault();
65+
this.reject();
66+
break;
67+
case 'Tab': {
68+
const last_button = document.querySelector('.jp-mod-accept');
69+
if (document.activeElement === last_button && !event.shiftKey) {
70+
event.stopPropagation();
71+
event.preventDefault();
72+
this.first.focus();
73+
}
74+
break;
75+
}
76+
case 'Enter':
77+
event.stopPropagation();
78+
event.preventDefault();
79+
this.resolve();
80+
break;
81+
default:
82+
break;
83+
}
84+
}
85+
}
86+
87+
function showCodeSnippetDialog<T>(
88+
options: Partial<Dialog.IOptions<T>> = {}
89+
): Promise<Dialog.IResult<T>> {
90+
const dialog = new CodeSnippetDialog(options);
91+
return dialog.launch();
4792
}
4893

4994
/**
@@ -97,7 +142,7 @@ export function showInputDialog(
97142
language: string,
98143
body: InputHandler
99144
): Promise<Contents.IModel | null> {
100-
return showDialog({
145+
return showCodeSnippetDialog({
101146
title: 'Save Code Snippet',
102147
body: body,
103148
buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'Save' })],
@@ -164,54 +209,7 @@ function createNewSnippet(
164209
});
165210

166211
codeSnippetWidget.renderCodeSnippetsSignal.emit(codeSnippetManager.snippets);
167-
showMessage({
168-
body: new MessageHandler(),
169-
});
170-
}
171-
172-
/**
173-
* Rename a file, warning for overwriting another.
174-
*/
175-
export async function saveOverWriteFile(
176-
codeSnippetManager: CodeSnippetService,
177-
oldSnippet: ICodeSnippet,
178-
newSnippet: ICodeSnippet
179-
): Promise<boolean> {
180-
const newName = newSnippet.name;
181-
182-
return await shouldOverwrite(newName).then((res) => {
183-
if (res) {
184-
newSnippet.id = oldSnippet.id;
185-
186-
codeSnippetManager.deleteSnippet(oldSnippet.id).then((res: boolean) => {
187-
if (!res) {
188-
console.log('Error in overwriting a snippet (delete)');
189-
return false;
190-
}
191-
});
192-
codeSnippetManager.addSnippet(newSnippet).then((res: boolean) => {
193-
if (!res) {
194-
console.log('Error in overwriting a snippet (add)');
195-
return false;
196-
}
197-
});
198-
return true;
199-
}
200-
});
201-
}
202-
203-
/**
204-
* Ask the user whether to overwrite a file.
205-
*/
206-
async function shouldOverwrite(newName: string): Promise<boolean> {
207-
const options = {
208-
title: 'Overwrite code snippet?',
209-
body: `"${newName}" already exists, overwrite?`,
210-
buttons: [Dialog.cancelButton(), Dialog.warnButton({ label: 'Overwrite' })],
211-
};
212-
return showDialog(options).then((result) => {
213-
return result.button.accept;
214-
});
212+
showMessage();
215213
}
216214

217215
/**
@@ -240,9 +238,15 @@ class InputHandler extends Widget {
240238
getValue(): string[] {
241239
const inputs = [];
242240
inputs.push(
243-
(this.node.getElementsByTagName('input')[0] as HTMLInputElement).value,
244-
(this.node.getElementsByTagName('input')[1] as HTMLInputElement).value,
245-
(this.node.getElementsByTagName('input')[2] as HTMLInputElement).value
241+
(this.node.querySelector(
242+
`.${CODE_SNIPPET_DIALOG_NAME_INPUT}`
243+
) as HTMLInputElement).value,
244+
(this.node.querySelector(
245+
`.${CODE_SNIPPET_DIALOG_DESC_INPUT}`
246+
) as HTMLInputElement).value,
247+
(this.node.querySelector(
248+
`.${CODE_SNIPPET_DIALOG_LANG_INPUT}`
249+
) as HTMLInputElement).value
246250
);
247251

248252
inputs.push(...Private.selectedTags);
@@ -254,12 +258,6 @@ class InputHandler extends Widget {
254258
}
255259
}
256260

257-
class MessageHandler extends Widget {
258-
constructor() {
259-
super({ node: Private.createConfirmMessageNode() });
260-
}
261-
}
262-
263261
/**
264262
* A namespace for private data.
265263
*/
@@ -284,22 +282,22 @@ class Private {
284282
const nameTitle = document.createElement('label');
285283
nameTitle.textContent = 'Snippet Name (required)';
286284
const name = document.createElement('input');
287-
name.className = CODE_SNIPPET_DIALOG_INPUT;
285+
name.className = CODE_SNIPPET_DIALOG_NAME_INPUT;
288286
name.required = true;
289287
name.placeholder = 'Ex. starter code';
290288
name.onblur = Private.handleOnBlur;
291289

292290
const descriptionTitle = document.createElement('label');
293291
descriptionTitle.textContent = 'Description (optional)';
294292
const description = document.createElement('input');
295-
description.className = CODE_SNIPPET_DIALOG_INPUT;
293+
description.className = CODE_SNIPPET_DIALOG_DESC_INPUT;
296294
description.placeholder = 'Description';
297295
description.onblur = Private.handleOnBlur;
298296

299297
const languageTitle = document.createElement('label');
300298
languageTitle.textContent = 'Language (required)';
301299
const languageInput = document.createElement('input');
302-
languageInput.className = CODE_SNIPPET_DIALOG_INPUT;
300+
languageInput.className = CODE_SNIPPET_DIALOG_LANG_INPUT;
303301
languageInput.setAttribute('list', 'languages');
304302
// capitalize the first character
305303
languageInput.value = language[0].toUpperCase() + language.slice(1);
@@ -360,7 +358,6 @@ class Private {
360358

361359
// replace the newTagName to input and delete plusIcon and insertbefore current tag on keydown or blur (refer to cell tags)
362360
static addTag(event: MouseEvent): boolean {
363-
event.preventDefault();
364361
const target = event.target as HTMLElement;
365362

366363
const plusIcon = document.querySelector(
@@ -380,7 +377,7 @@ class Private {
380377
static addTagOnKeyDown(event: KeyboardEvent): void {
381378
const inputElement = event.target as HTMLInputElement;
382379

383-
if (inputElement.value !== '' && event.keyCode === 13) {
380+
if (inputElement.value !== '' && event.key === 'Enter') {
384381
// duplicate tag
385382
if (Private.allTags.includes(inputElement.value)) {
386383
alert('Duplicate Tag Name!');
@@ -421,6 +418,9 @@ class Private {
421418
// reset InputElement
422419
inputElement.blur();
423420
event.stopPropagation();
421+
} else if (event.key === 'Escape') {
422+
inputElement.blur();
423+
event.stopPropagation();
424424
}
425425
}
426426

@@ -487,18 +487,4 @@ class Private {
487487
}
488488
return false;
489489
}
490-
491-
// create a confirm message when new snippet is created successfully
492-
static createConfirmMessageNode(): HTMLElement {
493-
const body = document.createElement('div');
494-
body.innerHTML = checkSVGstr;
495-
496-
const messageContainer = document.createElement('div');
497-
messageContainer.className = CODE_SNIPPET_CONFIRM_TEXT;
498-
const message = document.createElement('text');
499-
message.textContent = 'Saved as Snippet!';
500-
messageContainer.appendChild(message);
501-
body.append(messageContainer);
502-
return body;
503-
}
504490
}

0 commit comments

Comments
 (0)