Skip to content

Commit 45f8b50

Browse files
committed
[Notebook] hide trashcan during edit; add shortcut keys
1 parent 1fde0d9 commit 45f8b50

File tree

5 files changed

+200
-9
lines changed

5 files changed

+200
-9
lines changed

e2e/helper/notebookUtils.js

+21-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,25 @@ async function dragAndDropEmbed(page, notebookObject) {
7373
*/
7474
async function commitEntry(page) {
7575
//Click the Commit Entry button
76-
await page.locator('.c-ne__save-button > button').click();
76+
await page.getByLabel('Save notebook entry').click();
77+
}
78+
79+
/**
80+
* @private
81+
* @param {import('@playwright/test').Page} page
82+
*/
83+
async function commitEntryViaShortcutKey(page) {
84+
//Click the Commit Entry button
85+
await page.keyboard.press('Control+Enter');
86+
}
87+
88+
/**
89+
* @private
90+
* @param {import('@playwright/test').Page} page
91+
*/
92+
async function cancelEntry(page) {
93+
//Press the Escape key to cancel editing entry
94+
await page.keyboard.press('Escape');
7795
}
7896

7997
/**
@@ -153,7 +171,9 @@ async function createNotebookEntryAndTags(page, iterations = 1) {
153171

154172
export {
155173
addNotebookEntry,
174+
cancelEntry,
156175
commitEntry,
176+
commitEntryViaShortcutKey,
157177
createNotebookAndEntry,
158178
createNotebookEntryAndTags,
159179
dragAndDropEmbed,

e2e/tests/functional/plugins/notebook/notebook.e2e.spec.js

+158-1
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,133 @@ test.describe('Notebook entry tests', () => {
298298
await page.locator('.c-notebook__drag-area').click();
299299
await expect(page.getByLabel('Notebook Entry Input')).toBeVisible();
300300
await expect(page.getByLabel('Notebook Entry', { exact: true })).toHaveClass(/is-selected/);
301+
await expect(page.getByLabel('Save notebook entry')).toBeVisible();
301302
});
303+
304+
test("A new entry that's being created can be saved via ctrl+enter shortcut", async ({
305+
page
306+
}) => {
307+
// Navigate to the notebook object
308+
await page.goto(notebookObject.url);
309+
310+
// Click .c-notebook__drag-area to create a new notebook entry
311+
await page.locator('.c-notebook__drag-area').click();
312+
313+
//verify visible elements
314+
await expect(page.getByLabel('Notebook Entry Input')).toBeVisible();
315+
await expect(page.getByLabel('Delete this entry')).toBeHidden();
316+
await expect(page.getByLabel('Notebook Entry', { exact: true })).toHaveClass(/is-selected/);
317+
await expect(page.getByLabel('Save notebook entry')).toBeVisible();
318+
319+
//add text to textarea
320+
const testText = 'test text';
321+
await page.getByLabel('Notebook Entry Input').fill(testText);
322+
323+
const textareaVal = await page.getByLabel('Notebook Entry Input').inputValue();
324+
await expect(textareaVal).toBe(testText);
325+
326+
//press "Control+Enter" to save
327+
await nbUtils.commitEntryViaShortcutKey(page);
328+
329+
//verify notebook entry is saved
330+
await expect(page.locator('.c-ne__input')).toBeHidden();
331+
await expect(page.getByLabel('Notebook Entry Input')).toBeHidden();
332+
await expect(page.getByLabel('Save notebook entry')).toBeHidden();
333+
334+
await expect(page.getByLabel('Delete this entry')).toBeVisible();
335+
await expect(page.getByLabel('Notebook Entry Display')).toBeVisible();
336+
await expect(page.getByLabel('Notebook Entry Display')).toContainText(testText);
337+
});
338+
339+
test("A new entry that's being created can be canceled via the escape key", async ({ page }) => {
340+
// Navigate to the notebook object
341+
await page.goto(notebookObject.url);
342+
343+
// Click .c-notebook__drag-area to create a new notebook entry
344+
await page.locator('.c-notebook__drag-area').click();
345+
346+
//verify visible elements
347+
await expect(page.getByLabel('Notebook Entry Input')).toBeVisible();
348+
await expect(page.getByLabel('Delete this entry')).toBeHidden();
349+
await expect(page.getByLabel('Notebook Entry', { exact: true })).toHaveClass(/is-selected/);
350+
await expect(page.getByLabel('Save notebook entry')).toBeVisible();
351+
352+
//add text to textarea
353+
const testText = 'test text';
354+
await page.getByLabel('Notebook Entry Input').fill(testText);
355+
356+
const textareaVal = await page.getByLabel('Notebook Entry Input').inputValue();
357+
await expect(textareaVal).toBe(testText);
358+
359+
//press "Escape" key to cancel
360+
await nbUtils.cancelEntry(page);
361+
362+
//verify notebook entry is gone
363+
await expect(page.locator('.c-ne__input')).toBeHidden();
364+
await expect(page.getByLabel('Notebook Entry Input')).toBeHidden();
365+
await expect(page.getByLabel('Delete this entry')).toBeHidden();
366+
await expect(page.getByLabel('Save notebook entry')).toBeHidden();
367+
});
368+
369+
test("An existing entry that's being edited can be canceled via the escape key", async ({
370+
page
371+
}) => {
372+
// Navigate to the notebook object
373+
await page.goto(notebookObject.url);
374+
375+
const testText = 'test text';
376+
const otherText = 'other text';
377+
378+
//create notebook entry
379+
await nbUtils.enterTextEntry(page, testText);
380+
381+
//verify entry
382+
await expect(page.getByLabel('Notebook Entry', { exact: true })).toBeVisible();
383+
await expect(page.getByLabel('Notebook Entry Display')).toContainText(testText);
384+
385+
//make entry the active selection
386+
await page.getByLabel('Notebook Entry', { exact: true }).click();
387+
388+
//edit entry (make textarea visible for editing)
389+
await page.getByLabel('Notebook Entry Display', { exact: true }).click();
390+
await expect(page.getByLabel('Notebook Entry Input')).toBeVisible();
391+
392+
//edit textarea value
393+
await page.getByLabel('Notebook Entry Input').fill(otherText);
394+
const textareaVal = await page.getByLabel('Notebook Entry Input').inputValue();
395+
await expect(textareaVal).toBe(otherText);
396+
397+
//press "Escape" key
398+
await nbUtils.cancelEntry(page);
399+
400+
//verify entry reverts back to the original value
401+
await expect(page.getByLabel('Notebook Entry Input')).toBeHidden();
402+
await expect(page.getByLabel('Notebook Entry Display')).toContainText(testText);
403+
await expect(page.getByLabel('Notebook Entry Display')).not.toContainText(otherText);
404+
});
405+
406+
test('The trashcan is not visible when notebook entry is in edit mode', async ({ page }) => {
407+
// Navigate to the notebook object
408+
await page.goto(notebookObject.url);
409+
410+
const testText = 'test text';
411+
412+
//create notebook entry
413+
await nbUtils.enterTextEntry(page, testText);
414+
415+
//verify entry
416+
await expect(page.getByLabel('Notebook Entry', { exact: true })).toBeVisible();
417+
await expect(page.getByLabel('Delete this entry', { exact: true })).toBeVisible();
418+
419+
//make entry the active selection
420+
await page.getByLabel('Notebook Entry', { exact: true }).click();
421+
422+
//edit entry (make textarea visible for editing)
423+
await page.getByLabel('Notebook Entry Display', { exact: true }).click();
424+
await expect(page.getByLabel('Notebook Entry Input')).toBeVisible();
425+
await expect(page.getByLabel('Delete this entry', { exact: true })).toBeHidden();
426+
});
427+
302428
test('When an object is dropped into a notebook, a new entry is created and it should be focused', async ({
303429
page
304430
}) => {
@@ -351,7 +477,38 @@ test.describe('Notebook entry tests', () => {
351477
await expect(embed).toHaveClass(/icon-plot-overlay/);
352478
expect(embedName).toBe(overlayPlot.name);
353479
});
354-
test.fixme('new entries persist through navigation events without save', async ({ page }) => {});
480+
481+
test('new entries persist through navigation events without save', async ({ page }) => {
482+
// Navigate to the notebook object
483+
await page.goto(notebookObject.url);
484+
485+
// Click .c-notebook__drag-area to create a new notebook entry
486+
await page.locator('.c-notebook__drag-area').click();
487+
488+
//verify visible elements
489+
await expect(page.getByLabel('Notebook Entry', { exact: true })).toBeVisible();
490+
await expect(page.getByLabel('Notebook Entry Input')).toBeVisible();
491+
492+
//add text to textarea
493+
const testText = 'test text';
494+
await page.getByLabel('Notebook Entry Input').fill(testText);
495+
496+
const textareaVal = await page.getByLabel('Notebook Entry Input').inputValue();
497+
await expect(textareaVal).toBe(testText);
498+
499+
//navigate away from notebook without saving the new notebook entry
500+
await page.getByLabel('Navigate up to parent').click();
501+
await page.goto('./', { waitUntil: 'domcontentloaded' });
502+
503+
await expect(page.getByLabel('Notebook Entry', { exact: true })).toBeHidden();
504+
505+
//navigate back to notebook
506+
await page.goto(notebookObject.url);
507+
508+
await expect(page.getByLabel('Notebook Entry', { exact: true })).toBeVisible();
509+
await expect(page.getByLabel('Notebook Entry Display')).toContainText(testText);
510+
});
511+
355512
test('previous and new entries can be deleted', async ({ page }) => {
356513
// Navigate to the notebook object
357514
await page.goto(notebookObject.url);

e2e/tests/functional/plugins/notebook/notebookSnapshotImage.e2e.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ test.describe('Snapshot image tests', () => {
6060
}, fileData);
6161

6262
await page.dispatchEvent('.c-notebook__drag-area', 'drop', { dataTransfer: dropTransfer });
63-
await page.locator('.c-ne__save-button > button').click();
63+
await page.getByLabel('Save notebook entry').click();
6464
// be sure that entry was created
6565
await expect(page.getByText('favicon-96x96.png')).toBeVisible();
6666

e2e/tests/functional/plugins/notebook/notebookTags.e2e.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ test.describe('Tagging in Notebooks @addInit', () => {
163163

164164
await page.locator('text=To start a new entry, click here or drag and drop any object').click();
165165
await page.getByLabel('Notebook Entry Input').fill(`An entry without tags`);
166-
await page.locator('.c-ne__save-button > button').click();
166+
await page.getByLabel('Save notebook entry').click();
167167

168168
await page.hover('[aria-label="Notebook Entry Display"] >> nth=1');
169169
await page.locator('button[title="Delete this entry"]').last().click();

src/plugins/notebook/components/NotebookEntry.vue

+19-5
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
}}
4646
</span>
4747
</div>
48-
<span v-if="!readOnly && !isLocked" class="c-ne__local-controls--hidden">
48+
<span v-if="!readOnly && !isLocked && !editMode" class="c-ne__local-controls--hidden">
4949
<button
5050
class="c-ne__remove c-icon-button c-icon-button--major icon-trash"
5151
title="Delete this entry"
@@ -80,15 +80,21 @@
8080
v-else
8181
:id="entry.id"
8282
ref="entryInput"
83-
v-model="entry.text"
83+
v-model="entryTextVal"
8484
class="c-ne__input"
8585
aria-label="Notebook Entry Input"
8686
tabindex="-1"
8787
@mouseleave="canEdit = true"
88+
@keydown.esc="cancelEntry()"
89+
@keydown.ctrl.enter="updateEntryValue($event)"
8890
@blur="updateEntryValue($event)"
8991
></textarea>
9092
<div v-if="editMode" class="c-ne__save-button">
91-
<button class="c-button c-button--major icon-check"></button>
93+
<button
94+
class="c-button c-button--major icon-check"
95+
title="Save notebook entry (Ctrl-Enter)"
96+
aria-label="Save notebook entry"
97+
></button>
9298
</div>
9399
</template>
94100

@@ -271,6 +277,7 @@ export default {
271277
}
272278
},
273279
emits: [
280+
'cancel-edit',
274281
'delete-entry',
275282
'change-section-page',
276283
'update-entry',
@@ -283,7 +290,8 @@ export default {
283290
editMode: false,
284291
canEdit: true,
285292
enableEmbedsWrapperScroll: false,
286-
urlWhitelist: []
293+
urlWhitelist: [],
294+
entryTextVal: null
287295
};
288296
},
289297
computed: {
@@ -347,6 +355,7 @@ export default {
347355
this.renderer = new this.marked.Renderer();
348356
},
349357
mounted() {
358+
this.entryTextVal = this.entry.text;
350359
const originalLinkRenderer = this.renderer.link;
351360
this.renderer.link = this.validateLink.bind(this, originalLinkRenderer);
352361

@@ -492,6 +501,11 @@ export default {
492501
this.canEdit = false;
493502
}
494503
},
504+
cancelEntry() {
505+
this.editMode = false;
506+
this.entryTextVal = this.entry.text;
507+
this.$emit('cancel-edit');
508+
},
495509
deleteEntry() {
496510
this.$emit('delete-entry', this.entry.id);
497511
},
@@ -657,7 +671,7 @@ export default {
657671
},
658672
updateEntryValue($event) {
659673
this.editMode = false;
660-
const rawEntryValue = $event.target.value;
674+
const rawEntryValue = this.entryTextVal;
661675
const sanitizeInput = sanitizeHtml(rawEntryValue, { allowedAttributes: [], allowedTags: [] });
662676
// change &gt back to > for markdown to do blockquotes
663677
const restoredQuoteBrackets = sanitizeInput.replace(/&gt;/g, '>');

0 commit comments

Comments
 (0)