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' ;
55import { addIcon , checkIcon } from '@jupyterlab/ui-components' ;
66import { Contents } from '@jupyterlab/services' ;
77
88import { Widget } from '@lumino/widgets' ;
9- import { JSONObject } from '@lumino/coreutils ' ;
9+ import { Message } from '@lumino/messaging ' ;
1010
1111import { ICodeSnippet , CodeSnippetService } from './CodeSnippetService' ;
12+ import { showMessage } from './CodeSnippetConfirmMessage' ;
1213
1314import { CodeSnippetWidget } from './CodeSnippetWidget' ;
1415import { 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' ;
2930const CODE_SNIPPET_INPUTTAG_PLUS_ICON = 'jp-codeSnippet-inputTag-plusIcon' ;
3031const CODE_SNIPPET_INPUTTAG_LIST = 'jp-codeSnippet-inputTagList' ;
3132const CODE_SNIPPET_INPUT_TAG = 'jp-codeSnippet-inputTag' ;
3233const 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