From 120e4d321bc4abb8c635048b2f989eb76c13a090 Mon Sep 17 00:00:00 2001 From: jahn Date: Wed, 23 Sep 2020 12:50:57 -0700 Subject: [PATCH 1/6] Add save as code snippet command to non-notebook file --- src/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 67a226d..d52bdee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -169,12 +169,18 @@ function activateCodeSnippet( } }); - //Put the command above in context menu + // Put the saveCommand above in context menu app.contextMenu.addItem({ command: saveCommand, selector: '.jp-Cell' }); + // Put the saveCommand in non-notebook file context menu + app.contextMenu.addItem({ + command: saveCommand, + selector: '.jp-FileEditor' + }); + // Add keybinding to save app.commands.addKeyBinding({ command: saveCommand, From ee6cb9157fbf6be1a58a4571af1620814baab530 Mon Sep 17 00:00:00 2001 From: jahn Date: Thu, 24 Sep 2020 11:42:09 -0700 Subject: [PATCH 2/6] Fix #126 --- package.json | 7 ++++--- schema/settings.json | 10 ++++++++++ src/index.ts | 27 +++++++++++++++++---------- 3 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 schema/settings.json diff --git a/package.json b/package.json index bf1a707..906a1dd 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "author": "Jay Ahn, Kiran Pinnipati", "files": [ "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", - "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}" + "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}", + "schema/*.json" ], "main": "lib/index.js", "types": "lib/index.d.ts", @@ -43,7 +44,6 @@ "@jupyterlab/application": "^2.1.2", "@jupyterlab/apputils": "^2.2.4", "@jupyterlab/cells": "^2.2.4", - "@jupyterlab/coreutils": "^4.1.0", "@jupyterlab/docmanager": "^2.1.2", "@jupyterlab/docregistry": "^2.1.2", "@jupyterlab/fileeditor": "^2.1.2", @@ -77,7 +77,8 @@ "style/*.css" ], "jupyterlab": { - "extension": true + "extension": true, + "schemaDir": "schema" }, "husky": { "hooks": { diff --git a/schema/settings.json b/schema/settings.json new file mode 100644 index 0000000..9ef9fa1 --- /dev/null +++ b/schema/settings.json @@ -0,0 +1,10 @@ +{ + "jupyter.lab.shortcuts": [ + { + "command": "codeSnippet:save-as-snippet", + "keys": ["Accel Shift S"], + "selector": ".jp-Cell" + } + ], + "type": "object" +} diff --git a/src/index.ts b/src/index.ts index d52bdee..8dd8f1d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,8 @@ import { ILayoutRestorer } from '@jupyterlab/application'; import { ICommandPalette, WidgetTracker } from '@jupyterlab/apputils'; +import { ISettingRegistry } from '@jupyterlab/settingregistry'; + import { IEditorServices } from '@jupyterlab/codeeditor'; import { LabIcon } from '@jupyterlab/ui-components'; @@ -23,6 +25,7 @@ import { const CODE_SNIPPET_EXTENSION_ID = 'code-snippet-extension'; +const CODE_SNIPPET_SETTING_ID = 'jupyterlab-code-snippets:settings'; /** * Snippet Editor Icon */ @@ -150,7 +153,7 @@ function activateCodeSnippet( }); //Add an application command - const saveCommand = 'save as code snippet'; + const saveCommand = 'codeSnippet:save-as-snippet'; const toggled = false; app.commands.addCommand(saveCommand, { label: 'Save As Code Snippet', @@ -181,14 +184,6 @@ function activateCodeSnippet( selector: '.jp-FileEditor' }); - // Add keybinding to save - app.commands.addKeyBinding({ - command: saveCommand, - args: {}, - keys: ['Accel Shift S'], - selector: '.jp-Cell' - }); - // Track and restore the widget state const tracker = new WidgetTracker({ namespace: 'codeSnippetEditor' @@ -217,6 +212,18 @@ function activateCodeSnippet( }); } +const codeSnippetExtensionSetting: JupyterFrontEndPlugin = { + id: CODE_SNIPPET_SETTING_ID, + autoStart: true, + requires: [ISettingRegistry], + activate: (app: JupyterFrontEnd, settingRegistry: ISettingRegistry) => { + void settingRegistry + .load(CODE_SNIPPET_SETTING_ID) + .then(_ => console.log('settingRegistry successfully loaded!')) + .catch(e => console.log(e)); + } +}; + function getSelectedText(): string { let selectedText; // window.getSelection @@ -230,4 +237,4 @@ function getSelectedText(): string { return selectedText.toString(); } -export default code_snippet_extension; +export default [code_snippet_extension, codeSnippetExtensionSetting]; From 92da7a62d15986335f7a59d57e7cd32cadb25700 Mon Sep 17 00:00:00 2001 From: jahn Date: Tue, 29 Sep 2020 10:20:09 -0700 Subject: [PATCH 3/6] Change keyboard shortcut from accel shift s to accel shift a --- schema/settings.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/schema/settings.json b/schema/settings.json index 9ef9fa1..88710a4 100644 --- a/schema/settings.json +++ b/schema/settings.json @@ -2,8 +2,13 @@ "jupyter.lab.shortcuts": [ { "command": "codeSnippet:save-as-snippet", - "keys": ["Accel Shift S"], + "keys": ["Accel Shift A"], "selector": ".jp-Cell" + }, + { + "command": "codeSnippet:save-as-snippet", + "keys": ["Accel Shift A"], + "selector": ".jp-FileEditor" } ], "type": "object" From c84bdfa6e23f0fb2270ab3d25ea75c9b74d227bc Mon Sep 17 00:00:00 2001 From: jahn Date: Tue, 29 Sep 2020 12:10:40 -0700 Subject: [PATCH 4/6] Double click to rename code snippet #43 --- src/CodeSnippetDisplay.tsx | 69 ++++++++++++++++++++++++++++++++++- src/CodeSnippetWidgetModel.ts | 13 +++++++ style/index.css | 7 ++++ 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/CodeSnippetDisplay.tsx b/src/CodeSnippetDisplay.tsx index 006ae82..2ac865b 100644 --- a/src/CodeSnippetDisplay.tsx +++ b/src/CodeSnippetDisplay.tsx @@ -168,6 +168,7 @@ export class CodeSnippetDisplay extends React.Component< this._dragData = null; this.handleDragMove = this.handleDragMove.bind(this); this._evtMouseUp = this._evtMouseUp.bind(this); + this.handleRenameSnippet = this.handleRenameSnippet.bind(this); } // Handle code snippet insert into an editor @@ -321,9 +322,75 @@ export class CodeSnippetDisplay extends React.Component< ); } } - return {name}; + return {name}; }; + // rename snippet on double click + // TODO: duplicate name check! + private handleRenameSnippet( + event: React.MouseEvent + ): void { + const contentsService = CodeSnippetContentsService.getInstance(); + console.log(event.currentTarget); + console.log(event.target); + const target = event.target as HTMLElement; + const oldPath = 'snippets/' + target.innerHTML + '.json'; + + const new_element = document.createElement('input'); + new_element.setAttribute('type', 'text'); + new_element.id = 'jp-codeSnippet-rename'; + // new_element.innerHTML = target.innerHTML; + + target.replaceWith(new_element); + new_element.value = target.innerHTML; + + new_element.focus(); + new_element.setSelectionRange(0, new_element.value.length); + + new_element.onblur = (): void => { + if (target.innerHTML !== new_element.value) { + const newPath = 'snippets/' + new_element.value + '.json'; + + new_element.replaceWith(target); + + contentsService.rename(oldPath, newPath); + this.props._codeSnippetWidgetModel.renameSnippet( + target.innerHTML, + new_element.value + ); + + target.innerHTML = new_element.value; + } + }; + new_element.onkeydown = (event: KeyboardEvent): void => { + switch (event.code) { + case 'Enter' || 'NumpadEnter': // Enter + event.stopPropagation(); + event.preventDefault(); + new_element.blur(); + break; + case 'Escape': // Escape + event.stopPropagation(); + event.preventDefault(); + new_element.blur(); + break; + case 'ArrowUp': // Up arrow + event.stopPropagation(); + event.preventDefault(); + new_element.selectionStart = new_element.selectionEnd = 0; + break; + case 'ArrowDown': // Down arrow + event.stopPropagation(); + event.preventDefault(); + new_element.selectionStart = new_element.selectionEnd = + new_element.value.length; + break; + default: + break; + } + }; + } + private handleDragSnippet( event: React.MouseEvent ): void { diff --git a/src/CodeSnippetWidgetModel.ts b/src/CodeSnippetWidgetModel.ts index d4dc7e7..8998c1e 100644 --- a/src/CodeSnippetWidgetModel.ts +++ b/src/CodeSnippetWidgetModel.ts @@ -33,6 +33,19 @@ export class CodeSnippetWidgetModel implements ICodeSnippetWidgetModel { } } + renameSnippet(oldName: string, newName: string): void { + for (const snippet of this._snippets) { + if (snippet.name === oldName) { + snippet.name = newName; + CodeSnippetContentsService.getInstance().save( + 'snippets/' + snippet.name + '.json', + { type: 'file', format: 'text', content: JSON.stringify(snippet) } + ); + break; + } + } + } + addSnippet(newSnippet: ICodeSnippet, index: number): void { // append a new snippet created from input form to the end if (newSnippet.id === -1) { diff --git a/style/index.css b/style/index.css index 60bc2c4..48e3b30 100644 --- a/style/index.css +++ b/style/index.css @@ -69,6 +69,13 @@ background-color: var(--jp-layout-color2); } +#jp-codeSnippet-rename { + background-color: var(--jp-layout-color2); + border: 1px solid var(--jp-layout-color1); + border-radius: 2px; + font-size: var(--jp-ui-font-size1); +} + .jp-codeSnippet-metadata { flex-basis: 95%; width: 100%; From b837718d6ab37cce37366116380525531459f95a Mon Sep 17 00:00:00 2001 From: jahn Date: Tue, 29 Sep 2020 12:18:52 -0700 Subject: [PATCH 5/6] Fix changing back from input to span --- src/CodeSnippetDisplay.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CodeSnippetDisplay.tsx b/src/CodeSnippetDisplay.tsx index 2ac865b..51ccc12 100644 --- a/src/CodeSnippetDisplay.tsx +++ b/src/CodeSnippetDisplay.tsx @@ -348,11 +348,11 @@ export class CodeSnippetDisplay extends React.Component< new_element.setSelectionRange(0, new_element.value.length); new_element.onblur = (): void => { + new_element.replaceWith(target); + if (target.innerHTML !== new_element.value) { const newPath = 'snippets/' + new_element.value + '.json'; - new_element.replaceWith(target); - contentsService.rename(oldPath, newPath); this.props._codeSnippetWidgetModel.renameSnippet( target.innerHTML, From 907185b663cea8c2d35da4c55c89b53299c4d6e1 Mon Sep 17 00:00:00 2001 From: jahn Date: Thu, 1 Oct 2020 10:18:22 -0700 Subject: [PATCH 6/6] Handle duplicate snippet name --- src/CodeSnippetDisplay.tsx | 28 +++++++++++++++++++--------- style/index.css | 5 ++++- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/CodeSnippetDisplay.tsx b/src/CodeSnippetDisplay.tsx index 51ccc12..ded4d5f 100644 --- a/src/CodeSnippetDisplay.tsx +++ b/src/CodeSnippetDisplay.tsx @@ -327,9 +327,9 @@ export class CodeSnippetDisplay extends React.Component< // rename snippet on double click // TODO: duplicate name check! - private handleRenameSnippet( + private async handleRenameSnippet( event: React.MouseEvent - ): void { + ): Promise { const contentsService = CodeSnippetContentsService.getInstance(); console.log(event.currentTarget); console.log(event.target); @@ -339,7 +339,7 @@ export class CodeSnippetDisplay extends React.Component< const new_element = document.createElement('input'); new_element.setAttribute('type', 'text'); new_element.id = 'jp-codeSnippet-rename'; - // new_element.innerHTML = target.innerHTML; + new_element.innerHTML = target.innerHTML; target.replaceWith(new_element); new_element.value = target.innerHTML; @@ -347,20 +347,30 @@ export class CodeSnippetDisplay extends React.Component< new_element.focus(); new_element.setSelectionRange(0, new_element.value.length); - new_element.onblur = (): void => { - new_element.replaceWith(target); - + new_element.onblur = async (): Promise => { + console.log(target.innerHTML); + console.log(new_element.value); if (target.innerHTML !== new_element.value) { const newPath = 'snippets/' + new_element.value + '.json'; - - contentsService.rename(oldPath, newPath); + try { + await contentsService.rename(oldPath, newPath); + } catch (error) { + new_element.replaceWith(target); + + await showDialog({ + title: 'Duplicate Name of Code Snippet', + body:

{`"${newPath}" already exists.`}

, + buttons: [Dialog.okButton({ label: 'Dismiss' })] + }); + return; + } this.props._codeSnippetWidgetModel.renameSnippet( target.innerHTML, new_element.value ); - target.innerHTML = new_element.value; } + new_element.replaceWith(target); }; new_element.onkeydown = (event: KeyboardEvent): void => { switch (event.code) { diff --git a/style/index.css b/style/index.css index 48e3b30..e9a8857 100644 --- a/style/index.css +++ b/style/index.css @@ -42,6 +42,7 @@ text-overflow: ellipsis; color: var(--jp-ui-font-color0); display: flex; + align-items: center; } .jp-codeSnippetsContainer-button { @@ -72,8 +73,10 @@ #jp-codeSnippet-rename { background-color: var(--jp-layout-color2); border: 1px solid var(--jp-layout-color1); - border-radius: 2px; + border-radius: 4px; font-size: var(--jp-ui-font-size1); + box-sizing: border-box; + margin: 0px; } .jp-codeSnippet-metadata {