diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c99fc151..d07876905 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ [#381](https://github.com/nextcloud/cookbook/pull/381) @thembeat - Changing category name for all recipes in a category [#555](https://github.com/nextcloud/cookbook/pull/555/) @seyfeb - +- Functionality to reference other recipes by id in description, tools, ingredients, and instructions + [#562](https://github.com/nextcloud/cookbook/pull/562/) @seyfeb + ### Changed - Using computed property in recipe view [#522](https://github.com/nextcloud/cookbook/pull/522/) @seyfeb diff --git a/src/components/AppNavi.vue b/src/components/AppNavi.vue index a46be4227..91c6cd849 100644 --- a/src/components/AppNavi.vue +++ b/src/components/AppNavi.vue @@ -29,7 +29,7 @@ diff --git a/src/components/EditInputField.vue b/src/components/EditInputField.vue index 3d5d61f61..45f5491ac 100644 --- a/src/components/EditInputField.vue +++ b/src/components/EditInputField.vue @@ -1,21 +1,41 @@ diff --git a/src/components/EditMultiselectPopup.vue b/src/components/EditMultiselectPopup.vue new file mode 100644 index 000000000..52554990b --- /dev/null +++ b/src/components/EditMultiselectPopup.vue @@ -0,0 +1,114 @@ + + + + + + + diff --git a/src/components/RecipeEdit.vue b/src/components/RecipeEdit.vue index bd9a99b16..b1566885e 100644 --- a/src/components/RecipeEdit.vue +++ b/src/components/RecipeEdit.vue @@ -2,7 +2,7 @@
- + @@ -12,9 +12,10 @@ - - - + + + +
@@ -26,6 +27,7 @@ import EditInputField from './EditInputField' import EditInputGroup from './EditInputGroup' import EditMultiselect from './EditMultiselect' import EditMultiselectInputGroup from './EditMultiselectInputGroup' +import EditMultiselectPopup from './EditMultiselectPopup' import EditTimeField from './EditTimeField' export default { @@ -37,6 +39,7 @@ export default { EditMultiselect, EditTimeField, EditMultiselectInputGroup, + EditMultiselectPopup }, props: ['id'], data () { @@ -78,6 +81,7 @@ export default { isFetchingKeywords: true, allKeywords: [], selectedKeywords: [], + allRecipes: [], availableNutritionFields: [{ key: 'calories', label: t('cookbook', 'Calories'), placeholder: t('cookbook', 'E.g.: 450 kcal (amount & unit)') }, { key: 'carbohydrateContent', label: t('cookbook', 'Carbohydrate content'), placeholder: t('cookbook', 'E.g.: 2 g (amount & unit)') }, @@ -90,10 +94,18 @@ export default { { key: 'sodiumContent',label: t('cookbook', 'Sodium content'), placeholder: t('cookbook', 'E.g.: 2 g (amount & unit)') }, { key: 'sugarContent', label: t('cookbook', 'Sugar content'), placeholder: t('cookbook', 'E.g.: 2 g (amount & unit)') }, { key: 'transFatContent', label: t('cookbook', 'Trans-fat content'), placeholder: t('cookbook', 'E.g.: 2 g (amount & unit)') }, - { key: 'unsaturatedFatContent', label: t('cookbook', 'Unsaturated-fat content'), placeholder: t('cookbook', 'E.g.: 2 g (amount & unit)') }] + { key: 'unsaturatedFatContent', label: t('cookbook', 'Unsaturated-fat content'), placeholder: t('cookbook', 'E.g.: 2 g (amount & unit)') }], + referencesPopupFocused: false, + popupContext: undefined, + loadingRecipeReferences: true } }, computed: { + allrecipeOptions() { + return this.allRecipes.map(r => { + return {recipe_id: r.recipe_id, title: r.recipe_id + ": " + r.name} + }) + }, categoryUpdating() { return this.$store.state.categoryUpdating }, @@ -368,6 +380,8 @@ export default { } }, mounted () { + let $this = this + // Store the initial recipe configuration for possible later use if (this.recipeInit === null) { this.recipeInit = this.recipe @@ -397,7 +411,39 @@ export default { this.recipe.recipeCategory = val[0] } }) + // Register recipe-reference selection hook for showing popup when requested + // from a child element + this.$off('showRecipeReferencesPopup') + this.$on('showRecipeReferencesPopup', (val) => { + this.referencesPopupFocused = true + this.popupContext = val + }) + // Register hook when recipe reference has been selected in popup + this.$off('ms-popup-selected') + this.$on('ms-popup-selected', (opt) => { + this.referencesPopupFocused = false + this.popupContext.context.pasteString('r/' + opt.recipe_id + ' ') + }) + // Register hook when recipe reference has been selected in popup + this.$off('ms-popup-selection-canceled') + this.$on('ms-popup-selection-canceled', (opt) => { + this.referencesPopupFocused = false + this.popupContext.context.pasteCanceled() + }) this.savingRecipe = false + + // Load data for all recipes to be used in recipe-reference popup suggestions + axios.get(this.$window.baseUrl + '/api/recipes') + .then(function (response) { + $this.allRecipes = response.data + }) + .catch(function (e) { + console.log(e) + }) + .then(() => { + // finally + $this.loadingRecipeReferences = false + }) }, beforeDestroy() { window.removeEventListener('beforeunload', this.beforeWindowUnload) @@ -488,4 +534,13 @@ form fieldset ul label input[type="checkbox"] { cursor: pointer; } */ +.referencesPopup { + position: fixed; + display: none; +} + +.referencesPopup.visible { + display: block; +} + diff --git a/src/components/RecipeIngredient.vue b/src/components/RecipeIngredient.vue index bfdff6558..b600bd451 100644 --- a/src/components/RecipeIngredient.vue +++ b/src/components/RecipeIngredient.vue @@ -1,7 +1,7 @@ diff --git a/src/components/RecipeInstruction.vue b/src/components/RecipeInstruction.vue index 4a9130711..cf4ebcd0a 100644 --- a/src/components/RecipeInstruction.vue +++ b/src/components/RecipeInstruction.vue @@ -1,5 +1,5 @@ diff --git a/src/components/RecipeView.vue b/src/components/RecipeView.vue index c1600b9d5..9492121b9 100644 --- a/src/components/RecipeView.vue +++ b/src/components/RecipeView.vue @@ -25,7 +25,7 @@

- +

{{ t('cookbook', 'Source') }}: {{ $store.state.recipe.url }}

@@ -117,6 +117,7 @@ export default { computed: { recipe: function() { let recipe = { + description: '', ingredients: [], instructions: [], keywords: [], @@ -129,12 +130,23 @@ export default { nutrition: null } + if (this.$store.state.recipe.description) { + recipe.description = this.convertRecipeReferences( + this.escapeHtml(this.$store.state.recipe.description)) + } + if (this.$store.state.recipe.recipeIngredient) { recipe.ingredients = Object.values(this.$store.state.recipe.recipeIngredient) + .map((i) => { + return this.convertRecipeReferences(this.escapeHtml(i)) + }) } if (this.$store.state.recipe.recipeInstructions) { recipe.instructions = Object.values(this.$store.state.recipe.recipeInstructions) + .map((i) => { + return this.convertRecipeReferences(this.escapeHtml(i)) + }) } if (this.$store.state.recipe.keywords) { @@ -166,7 +178,9 @@ export default { } if (this.$store.state.recipe.tool) { - recipe.tools = this.$store.state.recipe.tool + recipe.tools = this.$store.state.recipe.tool.map((i) => { + return this.convertRecipeReferences(this.escapeHtml(i)) + }) } if (this.$store.state.recipe.dateCreated) { @@ -178,6 +192,7 @@ export default { let date = this.parseDateTime(this.$store.state.recipe.dateModified) recipe.dateModified = (date != null ? date.format('L, LT').toString() : null) } + if (this.$store.state.recipe.nutrition) { if ( this.$store.state.recipe.nutrition instanceof Array) { this.$store.state.recipe.nutrition = {} @@ -223,6 +238,20 @@ export default { } }, methods: { + escapeHtml: function(unsafeString) { + return unsafeString + .replace(/&/g, "&") + .replace(/\~/g, "˜") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + }, + convertRecipeReferences: function(text) { + let re = /(?<=^|\s|[,._+&?!-])#r\/(\d+)(?=$|\s|[.,_+&?!-])/g + let converted = text.replace(re, '#$1') + return converted + }, isNullOrEmpty: function(str) { return !str || typeof(str) === 'string' && 0 === str.trim().length; }, @@ -549,3 +578,13 @@ export default { content: '✔'; } + +