Skip to content

Commit 66bc95f

Browse files
authored
Merge pull request #173 from kpinnipa/inputValidation
Fix input and improve snippet creation.
2 parents f5a419e + cb0d23a commit 66bc95f

File tree

3 files changed

+84
-51
lines changed

3 files changed

+84
-51
lines changed

src/CodeSnippetContentsService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ContentsManager, Drive, Contents } from '@jupyterlab/services';
55

66
export interface ICodeSnippet {
77
name: string;
8-
description: string;
8+
description?: string;
99
language: string;
1010
// code separated by new line
1111
code: string[];

src/CodeSnippetEditor.tsx

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,14 @@ import { Message } from '@lumino/messaging';
2929

3030
import React from 'react';
3131

32-
import { CodeSnippetContentsService } from './CodeSnippetContentsService';
32+
import {
33+
CodeSnippetContentsService,
34+
ICodeSnippet
35+
} from './CodeSnippetContentsService';
3336
import { CodeSnippetWidget } from './CodeSnippetWidget';
3437
import { SUPPORTED_LANGUAGES } from './CodeSnippetLanguages';
3538
import { CodeSnippetEditorTags } from './CodeSnippetEditorTags';
39+
import { saveOverWriteFile } from './CodeSnippetInputDialog';
3640

3741
/**
3842
* CSS style classes
@@ -366,14 +370,14 @@ export class CodeSnippetEditor extends ReactWidget {
366370
message += 'Name must be filled out\n';
367371
status = false;
368372
}
369-
if (name.match(/[^a-z0-9_]+/)) {
373+
if (name.match(/[^a-zA-Z0-9_]+/)) {
370374
message += 'Wrong format of the name\n';
371375
status = false;
372376
}
373-
if (description === '') {
374-
message += 'Description must be filled out\n';
375-
status = false;
376-
}
377+
// if (description === '') {
378+
// message += 'Description must be filled out\n';
379+
// status = false;
380+
// }
377381
if (description.match(/[^a-zA-Z0-9_ ,.?!]+/)) {
378382
message += 'Wrong format of the description\n';
379383
status = false;
@@ -410,6 +414,8 @@ export class CodeSnippetEditor extends ReactWidget {
410414
const newPath =
411415
'snippets/' + this._codeSnippetEditorMetaData.name + '.json';
412416

417+
let nameCheck = false;
418+
413419
if (!this._codeSnippetEditorMetaData.fromScratch) {
414420
const oldPath = 'snippets/' + this.oldCodeSnippetName + '.json';
415421

@@ -425,45 +431,62 @@ export class CodeSnippetEditor extends ReactWidget {
425431
});
426432
return false;
427433
}
428-
429434
// set new name as an old name
430435
this.oldCodeSnippetName = this._codeSnippetEditorMetaData.name;
431436
}
432437
} else {
433-
let nameCheck = false;
434438
await this.contentsService
435439
.getData(newPath, 'file')
436440
.then(async (value: Contents.IModel) => {
437441
if (value.name) {
438-
await showDialog({
439-
title: 'Duplicate Name of Code Snippet',
440-
body: <p> {`"${newPath}" already exists.`} </p>,
441-
buttons: [Dialog.okButton({ label: 'Dismiss' })]
442-
});
442+
const oldSnippet: ICodeSnippet = JSON.parse(value.content);
443+
const newSnippet: ICodeSnippet = {
444+
name: this._codeSnippetEditorMetaData.name,
445+
description: this._codeSnippetEditorMetaData.description,
446+
language: this._codeSnippetEditorMetaData.language,
447+
code: this._codeSnippetEditorMetaData.code,
448+
id: this._codeSnippetEditorMetaData.id,
449+
tags: this._codeSnippetEditorMetaData.selectedTags
450+
};
451+
const result = saveOverWriteFile(
452+
this.codeSnippetWidget.codeSnippetWidgetModel,
453+
oldSnippet,
454+
newSnippet
455+
);
456+
await result
457+
.then(newSnippets => {
458+
this.codeSnippetWidget.renderCodeSnippetsSignal.emit(
459+
newSnippets
460+
);
461+
})
462+
.catch(_ => {
463+
console.log('cancelling overwrite!');
464+
return false;
465+
});
443466
}
467+
return true;
444468
})
445469
.catch(() => {
446470
nameCheck = true;
447471
});
448-
if (!nameCheck) {
449-
return false;
450-
}
451472
}
452473

453474
this.saved = true;
454475

455-
await this.contentsService.save(newPath, {
456-
type: 'file',
457-
format: 'text',
458-
content: JSON.stringify({
459-
name: this._codeSnippetEditorMetaData.name,
460-
description: this._codeSnippetEditorMetaData.description,
461-
language: this._codeSnippetEditorMetaData.language,
462-
code: this._codeSnippetEditorMetaData.code,
463-
id: this._codeSnippetEditorMetaData.id,
464-
tags: this._codeSnippetEditorMetaData.selectedTags
465-
})
466-
});
476+
if (nameCheck) {
477+
await this.contentsService.save(newPath, {
478+
type: 'file',
479+
format: 'text',
480+
content: JSON.stringify({
481+
name: this._codeSnippetEditorMetaData.name,
482+
description: this._codeSnippetEditorMetaData.description,
483+
language: this._codeSnippetEditorMetaData.language,
484+
code: this._codeSnippetEditorMetaData.code,
485+
id: this._codeSnippetEditorMetaData.id,
486+
tags: this._codeSnippetEditorMetaData.selectedTags
487+
})
488+
});
489+
}
467490

468491
// remove the dirty state
469492
this.title.className = this.title.className.replace(
@@ -585,18 +608,17 @@ export class CodeSnippetEditor extends ReactWidget {
585608
></input>
586609
<p className={CODE_SNIPPET_EDITOR_INPUTNAME_VALIDITY}>
587610
{
588-
'Name of the code snippet MUST be lowercased, alphanumeric or composed of underscore(_)'
611+
'Name of the code snippet MUST be alphanumeric or composed of underscore(_)'
589612
}
590613
</p>
591614
<label className={CODE_SNIPPET_EDITOR_LABEL}>
592-
Description (required)
615+
Description (optional)
593616
</label>
594617
<input
595618
className={CODE_SNIPPET_EDITOR_DESC_INPUT}
596619
defaultValue={this._codeSnippetEditorMetaData.description}
597620
placeholder={'Description'}
598621
type="text"
599-
required
600622
pattern={'[a-zA-Z0-9_ ,.?!]+'}
601623
onMouseDown={(
602624
event: React.MouseEvent<HTMLInputElement, MouseEvent>

src/CodeSnippetInputDialog.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,24 @@ export function CodeSnippetInputDialog(
7474
}
7575
}
7676

77+
const body: InputHandler = new InputHandler(tags);
78+
79+
return showInputDialog(tags, idx, codeSnippetWidget, code, body);
80+
}
81+
82+
/**
83+
* This function creates the actual input form and processes the inputs given.
84+
*/
85+
export function showInputDialog(
86+
tags: string[],
87+
idx: number,
88+
codeSnippetWidget: CodeSnippetWidget,
89+
code: string[],
90+
body: InputHandler
91+
): Promise<Contents.IModel | null> {
7792
return showCodeSnippetForm({
7893
title: 'Save Code Snippet',
79-
body: new InputHandler(tags),
80-
// focusNodeSelector: 'input',
94+
body: body,
8195
buttons: [
8296
CodeSnippetForm.cancelButton(),
8397
CodeSnippetForm.okButton({ label: 'Save' })
@@ -88,15 +102,15 @@ export function CodeSnippetInputDialog(
88102
}
89103

90104
if (validateForm(result) === false) {
91-
return CodeSnippetInputDialog(codeSnippetWidget, code, idx); // This works but it wipes out all the data they entered previously...
105+
showInputDialog(tags, idx, codeSnippetWidget, code, body);
92106
} else {
93107
if (idx === -1) {
94108
idx = codeSnippetWidget.codeSnippetWidgetModel.snippets.length;
95109
}
96110

97111
const tags = result.value.slice(3);
98112
const newSnippet: ICodeSnippet = {
99-
name: result.value[0].replace(' ', '').toLowerCase(),
113+
name: result.value[0].replace(' ', ''),
100114
description: result.value[1],
101115
language: result.value[2],
102116
code: code,
@@ -106,13 +120,13 @@ export function CodeSnippetInputDialog(
106120
const contentsService = CodeSnippetContentsService.getInstance();
107121
const currSnippets = codeSnippetWidget.codeSnippetWidgetModel.snippets;
108122
for (const snippet of currSnippets) {
109-
if (snippet.name === newSnippet.name) {
123+
if (snippet.name.toLowerCase() === newSnippet.name.toLowerCase()) {
110124
const result = saveOverWriteFile(
111125
codeSnippetWidget.codeSnippetWidgetModel,
112126
snippet,
113127
newSnippet
114128
);
115-
129+
console.log('uh reached here');
116130
result
117131
.then(newSnippets => {
118132
codeSnippetWidget.renderCodeSnippetsSignal.emit(newSnippets);
@@ -142,7 +156,7 @@ function createNewSnippet(
142156
content: JSON.stringify(newSnippet)
143157
}
144158
);
145-
159+
console.log(newSnippet.name);
146160
request.then(_ => {
147161
// add the new snippet to the snippet model
148162
codeSnippet.codeSnippetWidgetModel.addSnippet(newSnippet, newSnippet.id);
@@ -159,12 +173,12 @@ function createNewSnippet(
159173
/**
160174
* Rename a file, asking for confirmation if it is overwriting another.
161175
*/
162-
async function saveOverWriteFile(
176+
export async function saveOverWriteFile(
163177
codeSnippetWidgetModel: CodeSnippetWidgetModel,
164178
oldSnippet: ICodeSnippet,
165179
newSnippet: ICodeSnippet
166180
): Promise<ICodeSnippet[] | null> {
167-
const newPath = 'snippets/' + newSnippet.name + '.json';
181+
const newPath = 'snippets/' + oldSnippet.name + '.json';
168182

169183
return await shouldOverwrite(newPath).then(value => {
170184
if (value) {
@@ -182,7 +196,7 @@ async function saveOverWriteFile(
182196
/**
183197
* Ask the user whether to overwrite a file.
184198
*/
185-
async function shouldOverwrite(path: string): Promise<boolean> {
199+
export async function shouldOverwrite(path: string): Promise<boolean> {
186200
const options = {
187201
title: 'Overwrite code snippet?',
188202
body: `"${path}" already exists, overwrite?`,
@@ -219,20 +233,18 @@ export function validateForm(
219233
message += 'Name must be filled out\n';
220234
status = false;
221235
}
222-
if (name.match(/[^a-z0-9_]+/)) {
236+
if (name.match(/[^a-zA-Z0-9_]+/)) {
237+
//allow lowercase, uppercase, alphanumeric, and underscore
223238
message += 'Wrong format of the name\n';
224239
status = false;
225240
}
226-
if (description === '') {
227-
message += 'Description must be filled out\n';
228-
status = false;
229-
}
230241
if (description.match(/[^a-zA-Z0-9_ ,.?!]+/)) {
242+
//alphanumeric but can include space or punctuation
231243
message += 'Wrong format of the description\n';
232244
status = false;
233245
}
234246
if (language === '') {
235-
message += 'Language must be filled out';
247+
message += 'Language must be filled out\n';
236248
status = false;
237249
}
238250
if (!SUPPORTED_LANGUAGES.includes(language)) {
@@ -302,7 +314,7 @@ class Private {
302314
const body = document.createElement('form');
303315
const nameValidity = document.createElement('p');
304316
nameValidity.textContent =
305-
'Name of the code snippet MUST be lowercased, alphanumeric, or composed of underscore(_)';
317+
'Name of the code snippet MUST be alphanumeric, or composed of underscore(_)';
306318
nameValidity.className = CODE_SNIPPET_INPUTNAME_VALIDITY;
307319

308320
const descriptionValidity = document.createElement('p');
@@ -319,10 +331,9 @@ class Private {
319331
name.onblur = Private.handleOnBlur;
320332

321333
const descriptionTitle = document.createElement('label');
322-
descriptionTitle.textContent = 'Description (required)';
334+
descriptionTitle.textContent = 'Description (optional)';
323335
const description = document.createElement('input');
324336
description.className = CODE_SNIPPET_DIALOG_INPUT;
325-
description.required = true;
326337
description.pattern = '[a-zA-Z0-9_ ,.?!]+';
327338
description.onblur = Private.handleOnBlur;
328339

0 commit comments

Comments
 (0)