Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save edit form as draft using localStorage #4252

Open
wants to merge 71 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
de02d2d
Checkpoint
tiberiuichim Jan 10, 2023
aa9fbb7
Checkpoint
tiberiuichim Jan 10, 2023
4ad4b42
Use localStorage
tiberiuichim Jan 11, 2023
c5d1152
Merge remote-tracking branch 'origin/master' into draft_save
tiberiuichim Jan 11, 2023
a622d7f
Checkpoint
tiberiuichim Jan 11, 2023
4c93dab
Checkpoint
tiberiuichim Jan 11, 2023
6a0239d
Merge branch 'master' into draft_save
tiberiuichim Jan 11, 2023
4c0ff4d
Move hoc to helpers
tiberiuichim Jan 11, 2023
4fd716a
Allow any form to be saved
tiberiuichim Jan 12, 2023
5db5798
Simplify
tiberiuichim Jan 12, 2023
bdd7a64
Don't lose data that's outside schema
tiberiuichim Jan 12, 2023
142146c
Simplify
tiberiuichim Jan 12, 2023
ca0fe2e
Simplify
tiberiuichim Jan 12, 2023
4dfe4de
Simplify
tiberiuichim Jan 12, 2023
ddecddd
Merge branch 'master' into draft_save
avoinea Jun 7, 2023
48291b9
Merge branch 'master' of https://github.com/plone/volto into draft_save
Jun 12, 2023
e44e0a3
Merge branch 'master' into draft_save
avoinea Jun 16, 2023
af4ead9
Merge branch 'master' of https://github.com/plone/volto into draft_save
Jun 21, 2023
fb79e54
Merge branch 'draft_save' of https://github.com/plone/volto into draf…
Jun 21, 2023
7994949
feat: check for stale data, intl messages, fix double mount bug, hand…
Jun 28, 2023
c9e3032
merge master
Jun 29, 2023
8244b32
chore: helper index add usePrevious
Jun 29, 2023
0bd277c
fix: force click blocks-grid
Jun 29, 2023
4853753
chore: remove old saveAsDraft file
Jul 4, 2023
ace8963
chore: fix comments
Jul 5, 2023
1190514
chore: add cypress test for autosave
Jul 5, 2023
8bbd511
chore: add docs user manual
Jul 6, 2023
cd50390
chore: i18n
Jul 6, 2023
12c0edc
Update docs/source/user-manual/autosave.md
Jul 18, 2023
d4e2a59
Update docs/source/user-manual/autosave.md
Jul 18, 2023
920904c
Update docs/source/user-manual/autosave.md
Jul 18, 2023
7eae3e2
Update docs/source/user-manual/autosave.md
Jul 18, 2023
bd034e5
Update docs/source/user-manual/autosave.md
Jul 18, 2023
13073fe
Update docs/source/user-manual/autosave.md
Jul 18, 2023
93fcf16
Update docs/source/user-manual/autosave.md
Jul 18, 2023
4487691
Update docs/source/user-manual/autosave.md
Jul 18, 2023
f4f3e27
Update docs/source/user-manual/autosave.md
Jul 18, 2023
fb766a4
Update docs/source/user-manual/autosave.md
Jul 18, 2023
1fa4956
Update docs/source/user-manual/autosave.md
Jul 18, 2023
3367322
Update docs/source/user-manual/autosave.md
Jul 18, 2023
ce4a9b0
Update docs/source/user-manual/autosave.md
Jul 18, 2023
04304e7
Update docs/source/user-manual/autosave.md
Jul 18, 2023
461212b
Update docs/source/user-manual/autosave.md
Jul 18, 2023
026dc07
Update docs/source/user-manual/autosave.md
Jul 18, 2023
36dbe08
Update docs/source/user-manual/autosave.md
Jul 18, 2023
cde5090
Update docs/source/user-manual/autosave.md
Jul 19, 2023
26adfb3
Update docs/source/user-manual/autosave.md
Jul 19, 2023
874d764
chore: add an entry to docs/source/user-manual/index.md for autosave
Jul 19, 2023
886e9b8
chore: merge master
Jul 24, 2023
dee32a3
chore: fix cypress
Jul 26, 2023
d25c43d
chore: remove waitForResourceToLoad from tests
Jul 26, 2023
4067a3e
chore: autosave cypress delay
Aug 1, 2023
1dea385
chore: merge develop
Aug 2, 2023
2eca0bc
Merge branch 'master' into draft_save
sneridagh Sep 14, 2023
5879990
Update src/helpers/Utils/withSaveAsDraft.js
Sep 14, 2023
4752ef0
Update src/helpers/Utils/withSaveAsDraft.js
Sep 14, 2023
1ce23be
Update src/helpers/Utils/withSaveAsDraft.js
Sep 14, 2023
f5824e4
Update src/helpers/Utils/withSaveAsDraft.js
Sep 14, 2023
1db3c45
Update src/helpers/Utils/withSaveAsDraft.js
Sep 14, 2023
aa676b5
Update src/helpers/Utils/withSaveAsDraft.js
Sep 14, 2023
8e25395
Merge branch 'master' into draft_save
avoinea Sep 14, 2023
18dcdce
Fix tests, run i18n
sneridagh Sep 15, 2023
e9d4c3b
Merge branch 'master' into draft_save
sneridagh Sep 15, 2023
d6e39d1
merge master
Sep 26, 2023
5c22535
fix: eslint
Sep 26, 2023
c10520a
Merge branch 'master' of https://github.com/plone/volto into draft_save
Sep 26, 2023
eb842a8
Merge branch 'master' of https://github.com/plone/volto into draft_save
Sep 26, 2023
1b6ad37
Replace "load" with "restore".
stevepiercy Sep 27, 2023
8f269a6
Merge branch 'master' into draft_save
stevepiercy Sep 27, 2023
531d5d8
Attempt to clean up i18n errors in CI.
stevepiercy Sep 27, 2023
4b972da
Merge remote-tracking branch 'origin' main into draft_save
ichim-david Mar 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions docs/source/user-manual/autosave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
myst:
html_meta:
"description": "User manual for how Volto autosaves data in Plone 6."
"property=og:description": "User manual for how Volto autosaves data in Plone 6."
"property=og:title": "How to autosave content in Volto when editing, adding, or commenting on content."
"keywords": "Volto, Plone, frontend, React, User manual, autosave, restore"
---

(autosave-content-label)=

# Autosave content

The autosave feature allows you to restore locally saved data, in case of accidental browser close, refresh, quit, or change page.
It uses the [`localStorage` property](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
It clears the data when either the form is saved or you cancel restoring the local data.
If local data is found for the specific content, a toast is shown that allows you to either restore ({guilabel}`OK`) or discard ({guilabel}`Cancel`) it.
If local data is older than data on the server, it will still show the toast, but it will specify that the found local data is older than the server data.


(autosave-edit-mode-label)=

## Autosave edit mode

A local copy of the form is saved in `localStorage` when you start to edit, not when you merely open the page in edit mode.
Changing the form will update the `localStorage` with a new complete copy of the form.
In case you close the tab, quit, refresh, change the page, or cancel editing, when you revisit the page in edit mode, it will display a toast for the found data.
Data is saved with a unique id:

```js
const id = isEditForm
? ['form', type, pathname].join('-') // edit
: type
? ['form', pathname, type].join('-') // add
: schema?.properties?.comment
? ['form', pathname, 'comment'].join('-') // comments
: ['form', pathname].join('-');
```

Local data for the current content will be deleted, when you save the form or choose {guilabel}`Cancel` from the toast.


(autosave-new-content-label)=

## Autosave new content

When adding content, a copy of the form will be saved in `localStorage`, similar to edit mode.
But since the content hasn't been saved yet, we don't have an ID.
In this case the content type will be used.
Since it also uses the path to create the ID, the local data will be restored if you exit without saving, and only if you add the same content in the same path.


(autosave-comments-label)=

## Autosave comments

Comments are also saved locally, even though you are not in edit or add mode.
After restoring local data, if a comment is submitted, it will be deleted from `localStorage`.
1 change: 1 addition & 0 deletions docs/source/user-manual/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ Note that the audience for these sources may be a Developer, not an Editor, and

blocks
copy-paste-blocks
autosave
links-to-item
```
3 changes: 3 additions & 0 deletions news/4168.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add Auto-Save option. It will save a copy of the form, even comments even for adding a new content item. It detects if local data is less recent than server data.
Saving the form will delete the local data for the specific form.
User can choose to not restore the local data and this will also delete the local data. @tiberiuichim @rexalex @stevepiercy
225 changes: 225 additions & 0 deletions packages/volto/cypress/tests/core/basic/autosave.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
describe('createContent Tests', () => {
beforeEach(() => {
cy.autologin();
cy.createContent({
contentType: 'Document',
contentId: 'comments-page',
contentTitle: 'Comments Page',
allow_discussion: true,
});
cy.setRegistry(
'plone.app.discussion.interfaces.IDiscussionSettings.globally_enabled',
true,
);
cy.createContent({
contentType: 'Document',
contentId: 'my-first-page',
contentTitle: 'My First Page',
});
cy.createContent({
contentType: 'Document',
contentId: 'my-second-page',
contentTitle: 'My Second Page',
});
});

it('As editor I can autosave when editing a content item', () => {
cy.visit('/my-first-page');

cy.log('adding a text block on the first page');

cy.navigate('/my-first-page/edit');
cy.getSlateEditorAndType('My first text').contains('My first text');
cy.wait(1000);

cy.visit('/my-second-page');

cy.log('adding a text block on the second page');
cy.navigate('/my-second-page/edit');

cy.getSlateEditorAndType('My second text').contains('My second text');
cy.wait(1000);

cy.log('visit first page and start editing');

cy.visit('/my-first-page');
cy.navigate('/my-first-page/edit');
cy.wait(1000);

cy.findByRole('alert')
.get('.toast-inner-content')
.contains('Autosaved content found')
.get('button.ui.icon.button.save.toast-box')
.eq(0)
.click();

cy.wait(1000);

cy.getSlate().contains('My first text');

cy.log('visit second page and start editing');

cy.visit('/my-second-page');
cy.navigate('/my-second-page/edit');
cy.wait(1000);

cy.findByRole('alert')
.get('.toast-inner-content')
.contains('Autosaved content found')
.get('button.ui.icon.button.save.toast-box')
.eq(0)
.click();

cy.wait(1000);

cy.getSlate().contains('My second text');
cy.reload();

cy.log(
'test is cancel load data will delete from storage (toast does not show)',
);
cy.findByRole('alert')
.get('.toast-inner-content')
.contains('Autosaved content found')
.get('button.ui.icon.button.save.toast-box')
.eq(1)
.click();

cy.wait(1000);
cy.reload();

cy.wait(1000);

cy.contains('Autosaved content found').should('not.exist');
});

it('As editor I can autosave when adding a content item', function () {
cy.visit('/');

cy.log(
'adding a Document content type and refresh to verify if content is autosaved and retrieved',
);
cy.get('#toolbar-add').click().get('#toolbar-add-document').click();
cy.getSlateTitle().type('Page 1 title');
cy.getSlateEditorAndType('Page 1 content').contains('Page 1 content');
cy.wait(1000);
cy.reload();

cy.log('test if autosaved toast shows retrieved data and click OK to load');
cy.findByRole('alert')
.get('.toast-inner-content')
.contains('Autosaved content found')
.get('button.ui.icon.button.save.toast-box')
.eq(0)
.click();

cy.wait(1000);

cy.log('test if autosaved data is loaded');
cy.getSlateTitle().contains('Page 1 title');
cy.getSlate().contains('Page 1 content');

cy.log(
'test if draft is autosaved after I cancel adding a new page content type',
);

cy.get('button.button.cancel').click();

cy.wait(1000);

cy.get('#toolbar-add').click().get('#toolbar-add-document').click();

cy.findByRole('alert')
.get('.toast-inner-content')
.contains('Autosaved content found')
.get('button.ui.icon.button.save.toast-box')
.eq(0)
.click();

cy.wait(1000);
cy.getSlateTitle().contains('Page 1 title');
cy.getSlate().contains('Page 1 content');

cy.wait(1000);

cy.log('test if page content type is added as new page after Toolbar Save');

cy.get('#toolbar-save').focus().click();
cy.wait(2000);
cy.contains('Page 1 title');
cy.wait(1000);

cy.log('test draft is deleted from local storage after save');

cy.visit('/');
cy.get('#toolbar-add').click().get('#toolbar-add-document').click();

cy.wait(1000);

cy.contains('Autosaved content found').should('not.exist');
});

it('As editor I can autosave comments', function () {
cy.log('adding a comment and refresh,');
cy.visit('/comments-page');
cy.get('textarea[id="field-comment"]').clear().type('This is a comment');
cy.wait(1000);
cy.reload();

cy.log('test if comment is retrieved from local storage');

cy.findByRole('alert')
.get('.toast-inner-content')
.contains('Autosaved content found')
.get('button.ui.icon.button.save.toast-box')
.eq(0)
.click();

cy.get('#field-comment').contains('This is a comment');

cy.wait(1000);
cy.reload();

cy.log(
'test if comment is deleted from local storage after selecting Cancel in the Autosave toast',
);

cy.findByRole('alert')
.get('.toast-inner-content')
.contains('Autosaved content found')
.get('button.ui.icon.button.save.toast-box')
.eq(1)
.click();

cy.wait(1000);
cy.reload();
cy.contains('Autosaved content found').should('not.exist');

cy.log('adding another comment and save it');

cy.get('textarea[id="field-comment"]')
.clear()
.type('This is a another comment');
cy.wait(1000);
cy.reload();

cy.findByRole('alert')
.get('.toast-inner-content')
.contains('Autosaved content found')
.get('button.ui.icon.button.save.toast-box')
.eq(0)
.click();

cy.wait(1000);

cy.get('button[type="submit"').click();

cy.get('.comment').contains('This is a another comment');

cy.log('test if the local storage comment was deleted after submit');

cy.wait(1000);
cy.reload();
cy.contains('Autosaved content found').should('not.exist');
});
});
4 changes: 2 additions & 2 deletions packages/volto/cypress/tests/core/blocks/blocks-grid.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ context('Blocks Acceptance Tests', () => {
cy.get('button[aria-label="Add block in position 1"]').click();
cy.get('.blocks-chooser [aria-label="Unfold Text blocks"]').click();
cy.wait(200);
cy.get('.blocks-chooser .text .button.slate').click();
cy.get('.blocks-chooser .text .button.slate').click({ force: true });
cy.getSlateEditorSelectorAndType(
'.block.gridBlock.selected .slate-editor [contenteditable=true]',
'Colorless green ideas sleep furiously.',
Expand Down Expand Up @@ -90,7 +90,7 @@ context('Blocks Acceptance Tests', () => {
cy.get('button[aria-label="Add block in position 1"]').click();
cy.get('.blocks-chooser [aria-label="Unfold Text blocks"]').click();
cy.wait(200);
cy.get('.blocks-chooser .text .button.slate').click();
cy.get('.blocks-chooser .text .button.slate').click({ force: true });
cy.scrollTo('top');

cy.getSlateEditorSelectorAndType(
Expand Down
15 changes: 15 additions & 0 deletions packages/volto/locales/ca/LC_MESSAGES/volto.po
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,11 @@ msgstr ""
msgid "Assignments"
msgstr ""

#. Default: "Autosaved content found"
#: helpers/Utils/withSaveAsDraft
msgid "Autosaved content found"
msgstr ""

#. Default: "Available"
#: components/manage/Controlpanels/AddonsControlpanel
msgid "Available"
Expand Down Expand Up @@ -1109,6 +1114,11 @@ msgstr "Realment voleu suprimir l'usuari {username}?"
msgid "Do you really want to delete this item?"
msgstr "Realment voleu suprimir aquest element?"

#. Default: "Do you want to restore the autosaved content?"
#: helpers/Utils/withSaveAsDraft
msgid "Do you want to restore the autosaved content?"
msgstr ""

#. Default: "Document"
#: components/manage/Multilingual/TranslationObject
#: components/manage/Sidebar/Sidebar
Expand Down Expand Up @@ -3636,6 +3646,11 @@ msgstr "El procés de registre ha estat satisfactori. Si us plau, comproveu la v
msgid "The site configuration is outdated and needs to be upgraded."
msgstr ""

#. Default: "The version of the autosaved content I found in your browser is older than that stored on the server. Do you want to restore the autosaved content? (You can undo the autosaved content and revert to the server version.)"
#: helpers/Utils/withSaveAsDraft
msgid "The version of the autosaved content I found in your browser is older than that stored on the server. Do you want to restore the autosaved content? (You can undo the autosaved content and revert to the server version.)"
msgstr ""

#. Default: "The working copy was discarded"
#: components/manage/Toolbar/More
msgid "The working copy was discarded"
Expand Down
15 changes: 15 additions & 0 deletions packages/volto/locales/de/LC_MESSAGES/volto.po
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,11 @@ msgstr ""
msgid "Assignments"
msgstr "Zuweisungen"

#. Default: "Autosaved content found"
#: helpers/Utils/withSaveAsDraft
msgid "Autosaved content found"
msgstr ""

#. Default: "Available"
#: components/manage/Controlpanels/AddonsControlpanel
msgid "Available"
Expand Down Expand Up @@ -1108,6 +1113,11 @@ msgstr "Möchten Sie den Nutzer {username} wirklich löschen?"
msgid "Do you really want to delete this item?"
msgstr "Möchten Sie den Artikel wirklich löschen?"

#. Default: "Do you want to restore the autosaved content?"
#: helpers/Utils/withSaveAsDraft
msgid "Do you want to restore the autosaved content?"
msgstr ""

#. Default: "Document"
#: components/manage/Multilingual/TranslationObject
#: components/manage/Sidebar/Sidebar
Expand Down Expand Up @@ -3635,6 +3645,11 @@ msgstr "Bitte prüfen Sie Ihr E-Mail Postfach. Sie sollten eine E-Mail erhalten
msgid "The site configuration is outdated and needs to be upgraded."
msgstr "Die Seitenkonfiguration ist veraltet und muss aktualisiert werden."

#. Default: "The version of the autosaved content I found in your browser is older than that stored on the server. Do you want to restore the autosaved content? (You can undo the autosaved content and revert to the server version.)"
#: helpers/Utils/withSaveAsDraft
msgid "The version of the autosaved content I found in your browser is older than that stored on the server. Do you want to restore the autosaved content? (You can undo the autosaved content and revert to the server version.)"
msgstr ""

#. Default: "The working copy was discarded"
#: components/manage/Toolbar/More
msgid "The working copy was discarded"
Expand Down
Loading
Loading