diff --git a/CHANGELOG.md b/CHANGELOG.md index 877b98dd4..564fe9c36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,13 +9,13 @@ [#346](https://github.com/nextcloud/cookbook/pull/346) @christianlupus - CI: Create source code packages for each commit [#346](https://github.com/nextcloud/cookbook/pull/346) @christianlupus -- Allow for inconsistient schema: Parse instructions as list of elements +- Allow for inconsistent schema: Parse instructions as list of elements [#347](https://github.com/nextcloud/cookbook/pull/347) @victorjoos - Show button to view all recipes without a category [#362](https://github.com/nextcloud/cookbook/pull/362/) @seyfeb - Add a basic changelog to the repository [#366](https://github.com/nextcloud/cookbook/pull/366/) @christianlupus -- Enfoce update of changelog through CI +- Enforce update of changelog through CI [#366](https://github.com/nextcloud/cookbook/pull/366/) @christianlupus - Keyword cloud is displayed in recipe [#373](https://github.com/nextcloud/cookbook/pull/373/) @seyfeb @@ -41,10 +41,12 @@ [#403](https://github.com/nextcloud/cookbook/pull/403) @thembeat - Nutrition information display and editing [#416](https://github.com/nextcloud/cookbook/pull/416/) @seyfeb +- Asking user for confirmation when leaving recipe-editor form with changes + [#464](https://github.com/nextcloud/cookbook/pull/464/) @seyfeb ### Changed -- Switch of project ownership to neextcloud organization in GitHub -- Chanage in issue managment +- Switch of project ownership to nextcloud organization in GitHub +- Change in issue management - Changes to `.gitignore` file - Translation issues - Added available Android apps to README @@ -52,11 +54,11 @@ [#376](https://github.com/nextcloud/cookbook/pull/376) @christianlupus - Made the layout more responsive to shift the metadata right of the image in very wide screens [#349](https://github.com/nextcloud/cookbook/pull/349/) @christianlupus -- Optimization for SVG to reduce the filesize +- Optimization for SVG to reduce the file size [#404](https://github.com/nextcloud/cookbook/pull/404) @thembeat - Replace Default Recipe-Thumb and Full Image with SVG [#418](https://github.com/nextcloud/cookbook/pull/418) @thembeat -- ENhance the CI tests and build valid dist tarballs during the CI runs +- Enhance the CI tests and build valid dist tarballs during the CI runs [#435](https://github.com/nextcloud/cookbook/pull/435) @christianlupus - Images in recipe list are lazily loaded [#413](https://github.com/nextcloud/cookbook/pull/413/) @seyfeb diff --git a/src/components/RecipeEdit.vue b/src/components/RecipeEdit.vue index c05d40983..4e02f02a0 100644 --- a/src/components/RecipeEdit.vue +++ b/src/components/RecipeEdit.vue @@ -60,7 +60,13 @@ export default { // keep it up to date in multiple places if it changes later recipeInit: null, + // ========================== // These are helper variables + + // Changes have been made to the initial values of the form + formDirty: false, + // the save button has been clicked + savingRecipe: false, prepTime: { time: [0, 0], paddedTime: '' }, cookTime: { time: [0, 0], paddedTime: '' }, totalTime: { time: [0, 0], paddedTime: '' }, @@ -109,6 +115,12 @@ export default { // convert keyword array to comma-separated string this.recipe['keywords'] = this.selectedKeywords.join() } + }, + recipe: { + deep: true, + handler() { + this.formDirty = true + } } }, methods: { @@ -129,6 +141,19 @@ export default { addEntry: function(field, index, content='') { this.recipe[field].splice(index, 0, content) }, + beforeWindowUnload(e) { + if (this.confirmStayInEditedForm()) { + // Cancel the window unload event + e.preventDefault() + e.returnValue = '' + } + }, + confirmLeavingPage() { + return window.confirm('You have unsaved changes! Do you still want to leave?') + }, + confirmStayInEditedForm() { + return !this.savingRecipe && this.formDirty && !this.confirmLeavingPage() + }, deleteEntry: function(field, index) { this.recipe[field].splice(index, 1) }, @@ -216,6 +241,7 @@ export default { }) }, save: function() { + this.savingRecipe = true this.$store.dispatch('setSavingRecipe', { saving: true }) let $this = this if (this.recipe.id) { @@ -232,6 +258,8 @@ export default { }).fail(function(e) { $this.$store.dispatch('setSavingRecipe', { saving: false }) alert(t('cookbook', 'Recipe could not be saved')) + }).always(() => { + $this.savingRecipe = false }) } else { // Create a new recipe @@ -247,6 +275,8 @@ export default { }).fail(function(e) { $this.$store.dispatch('setSavingRecipe', { saving: false }) alert(t('cookbook', 'Recipe could not be saved')) + }).always(() => { + $this.savingRecipe = false }) } }, @@ -287,14 +317,17 @@ export default { this.allCategories.push(this.recipe['recipeCategory']) } - // Always set the active page last! this.$store.dispatch('setPage', { page: 'edit' }) } else { this.initEmptyRecipe() this.$store.dispatch('setPage', { page: 'create' }) } - this.recipeInit = this.recipe + this.recipeInit = JSON.parse(JSON.stringify(this.recipe)) + this.$nextTick(function() { + this.formDirty = false + }) + }, initEmptyRecipe: function() { this.prepTime = { time: [0, 0], paddedTime: '' } @@ -318,6 +351,7 @@ export default { recipeInstructions: [], nutrition: {} } + this.formDirty = false } }, mounted () { @@ -337,6 +371,10 @@ export default { this.$root.$on('reloadRecipeEdit', () => { this.loadRecipeData() }) + this.savingRecipe = false + }, + beforeDestroy() { + window.removeEventListener('beforeunload', this.beforeWindowUnload) }, // We can check if the user has browsed from the same recipe's view to this // edit and save some time by not reloading the recipe data, leading to a @@ -363,13 +401,19 @@ export default { * This can also be used to confirm that the user wants to leave the page * if there are unsaved changes. */ - beforeRouteLeave (to, from, next) { +beforeRouteLeave (to, from, next) { // beforeRouteLeave is called when the static route changes. - // We have to check if the target component stays the same and reload. - // However, we should not reload if the component changes; otherwise - // reloaded data may overwrite the data loaded at the target component - // which will at the very least result in incorrect breadcrumb path! - next() + // Cancel the navigation, if the form has unsaved edits and the user did not + // confirm leave. This prevents accidentally losing changes + if (this.confirmStayInEditedForm()){ + next(false) + } else { + // We have to check if the target component stays the same and reload. + // However, we should not reload if the component changes; otherwise + // reloaded data may overwrite the data loaded at the target component + // which will at the very least result in incorrect breadcrumb path! + next() + } // Check if we should reload the component content if (this.$window.shouldReloadContent(from.fullPath, to.fullPath)) { this.setup() @@ -383,7 +427,9 @@ export default { this.setup() } }, - + created() { + window.addEventListener('beforeunload', this.beforeWindowUnload) + }, }