1
1
// Copyright (c) 2020, jupytercalpoly
2
2
// Distributed under the terms of the BSD-3 Clause License.
3
3
4
- import { showDialog , Dialog } from '@jupyterlab/apputils' ;
4
+ import { Dialog } from '@jupyterlab/apputils' ;
5
5
import { addIcon , checkIcon } from '@jupyterlab/ui-components' ;
6
6
import { Contents } from '@jupyterlab/services' ;
7
7
8
8
import { Widget } from '@lumino/widgets' ;
9
- import { JSONObject } from '@lumino/coreutils ' ;
9
+ import { Message } from '@lumino/messaging ' ;
10
10
11
11
import { ICodeSnippet , CodeSnippetService } from './CodeSnippetService' ;
12
+ import { showMessage } from './CodeSnippetConfirmMessage' ;
12
13
13
14
import { CodeSnippetWidget } from './CodeSnippetWidget' ;
14
15
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' ;
19
17
20
18
/**
21
19
* The class name added to file dialogs.
@@ -25,25 +23,72 @@ const FILE_DIALOG_CLASS = 'jp-codeSnippet-fileDialog';
25
23
/**
26
24
* CSS STYLING
27
25
*/
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' ;
29
30
const CODE_SNIPPET_INPUTTAG_PLUS_ICON = 'jp-codeSnippet-inputTag-plusIcon' ;
30
31
const CODE_SNIPPET_INPUTTAG_LIST = 'jp-codeSnippet-inputTagList' ;
31
32
const CODE_SNIPPET_INPUT_TAG = 'jp-codeSnippet-inputTag' ;
32
33
const CODE_SNIPPET_INPUT_TAG_CHECK = 'jp-codeSnippet-inputTag-check' ;
33
- const CODE_SNIPPET_CONFIRM_TEXT = 'jp-codeSnippet-confirm-text' ;
34
34
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 ( ) ;
47
92
}
48
93
49
94
/**
@@ -97,7 +142,7 @@ export function showInputDialog(
97
142
language : string ,
98
143
body : InputHandler
99
144
) : Promise < Contents . IModel | null > {
100
- return showDialog ( {
145
+ return showCodeSnippetDialog ( {
101
146
title : 'Save Code Snippet' ,
102
147
body : body ,
103
148
buttons : [ Dialog . cancelButton ( ) , Dialog . okButton ( { label : 'Save' } ) ] ,
@@ -164,54 +209,7 @@ function createNewSnippet(
164
209
} ) ;
165
210
166
211
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 ( ) ;
215
213
}
216
214
217
215
/**
@@ -240,9 +238,15 @@ class InputHandler extends Widget {
240
238
getValue ( ) : string [ ] {
241
239
const inputs = [ ] ;
242
240
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
246
250
) ;
247
251
248
252
inputs . push ( ...Private . selectedTags ) ;
@@ -254,12 +258,6 @@ class InputHandler extends Widget {
254
258
}
255
259
}
256
260
257
- class MessageHandler extends Widget {
258
- constructor ( ) {
259
- super ( { node : Private . createConfirmMessageNode ( ) } ) ;
260
- }
261
- }
262
-
263
261
/**
264
262
* A namespace for private data.
265
263
*/
@@ -284,22 +282,22 @@ class Private {
284
282
const nameTitle = document . createElement ( 'label' ) ;
285
283
nameTitle . textContent = 'Snippet Name (required)' ;
286
284
const name = document . createElement ( 'input' ) ;
287
- name . className = CODE_SNIPPET_DIALOG_INPUT ;
285
+ name . className = CODE_SNIPPET_DIALOG_NAME_INPUT ;
288
286
name . required = true ;
289
287
name . placeholder = 'Ex. starter code' ;
290
288
name . onblur = Private . handleOnBlur ;
291
289
292
290
const descriptionTitle = document . createElement ( 'label' ) ;
293
291
descriptionTitle . textContent = 'Description (optional)' ;
294
292
const description = document . createElement ( 'input' ) ;
295
- description . className = CODE_SNIPPET_DIALOG_INPUT ;
293
+ description . className = CODE_SNIPPET_DIALOG_DESC_INPUT ;
296
294
description . placeholder = 'Description' ;
297
295
description . onblur = Private . handleOnBlur ;
298
296
299
297
const languageTitle = document . createElement ( 'label' ) ;
300
298
languageTitle . textContent = 'Language (required)' ;
301
299
const languageInput = document . createElement ( 'input' ) ;
302
- languageInput . className = CODE_SNIPPET_DIALOG_INPUT ;
300
+ languageInput . className = CODE_SNIPPET_DIALOG_LANG_INPUT ;
303
301
languageInput . setAttribute ( 'list' , 'languages' ) ;
304
302
// capitalize the first character
305
303
languageInput . value = language [ 0 ] . toUpperCase ( ) + language . slice ( 1 ) ;
@@ -360,7 +358,6 @@ class Private {
360
358
361
359
// replace the newTagName to input and delete plusIcon and insertbefore current tag on keydown or blur (refer to cell tags)
362
360
static addTag ( event : MouseEvent ) : boolean {
363
- event . preventDefault ( ) ;
364
361
const target = event . target as HTMLElement ;
365
362
366
363
const plusIcon = document . querySelector (
@@ -380,7 +377,7 @@ class Private {
380
377
static addTagOnKeyDown ( event : KeyboardEvent ) : void {
381
378
const inputElement = event . target as HTMLInputElement ;
382
379
383
- if ( inputElement . value !== '' && event . keyCode === 13 ) {
380
+ if ( inputElement . value !== '' && event . key === 'Enter' ) {
384
381
// duplicate tag
385
382
if ( Private . allTags . includes ( inputElement . value ) ) {
386
383
alert ( 'Duplicate Tag Name!' ) ;
@@ -421,6 +418,9 @@ class Private {
421
418
// reset InputElement
422
419
inputElement . blur ( ) ;
423
420
event . stopPropagation ( ) ;
421
+ } else if ( event . key === 'Escape' ) {
422
+ inputElement . blur ( ) ;
423
+ event . stopPropagation ( ) ;
424
424
}
425
425
}
426
426
@@ -487,18 +487,4 @@ class Private {
487
487
}
488
488
return false ;
489
489
}
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
- }
504
490
}
0 commit comments