Merge remote-tracking branch 'upstream/master' into document-development-environment

Signed-off-by: Christian Wolf <github@christianwolf.email>
This commit is contained in:
Christian Wolf 2022-03-05 19:25:04 +01:00
Родитель 94b4c0a159 1e409c5ecd
Коммит 98f44587b6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 9FC3120E932F73F1
20 изменённых файлов: 248 добавлений и 83 удалений

2
.github/actions/deploy/patch поставляемый
Просмотреть файл

@ -1 +1 @@
9
10

Просмотреть файл

@ -1,5 +1,16 @@
## [Unreleased]
### Fixed
- Reduce complex coupling between event handlers in EditInputGroup.vue
[#901](https://github.com/nextcloud/cookbook/pull/901) @MarcelRobitaille
## 0.9.10 - 2022-03-04
### Added
- Remove prefix of pasted content for better formatting
[#887](https://github.com/nextcloud/cookbook/pull/887) @MarcelRobitaille
### Fixed
- Added app info XML back to allow automatic translations
[#878](https://github.com/nextcloud/cookbook/pull/878) @christianlupus
@ -9,6 +20,22 @@
[#880](https://github.com/nextcloud/cookbook/pull/880) @christianlupus
- Usage of caches for NPM speedup
[#883](https://github.com/nextcloud/cookbook/pull/883) @christianlupus
- Make the controls sticky on top
[#888](https://github.com/nextcloud/cookbook/pull/888) @MarcelRobitaille
- Cleanup code related to pasting
[#886](https://github.com/nextcloud/cookbook/pull/886) @MarcelRobitaille
- Make height of control header dependant on server CSS variable
[#897](https://github.com/nextcloud/cookbook/pull/897) @MarcelRobitaille
- Fix UI glitch when keyword list is empty
[#892](https://github.com/nextcloud/cookbook/pull/892) @MarcelRobitaille
- Allow switching to new instruction line with Enter key
[#890](https://github.com/nextcloud/cookbook/pull/890) @MarcelRobitaille
- Prevent inserting newline characters in instructions/ingredients/tools when pressing enter
[#900](https://github.com/nextcloud/cookbook/pull/900) @MarcelRobitaille
### Documentation
- Added clarification between categories and keywords for users
[#889](https://github.com/nextcloud/cookbook/pull/889) @MarcelRobitaille
## 0.9.9 - 2022-01-13

Двоичные данные
docs/user/assets/keywords-and-categories.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 52 KiB

Двоичные данные
docs/user/assets/keywords-and-categories.xcf Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -5,7 +5,7 @@
### Importing from a Website
Recipes can be imported by entering the URL to a recipe in the text-input field in the top right area of the Cookbook app.
Recipes can be imported by entering the URL to a recipe in the text-input field in the top right area of the Cookbook app.
<img src="assets/create_import.png" alt="Recipe-import field" width="200px" />
@ -23,4 +23,25 @@ Currently, the only way to share recipes is by sharing the Nextcloud folder that
### Public Sharing
At the moment it is not possible to share a public link to a recipe.
At the moment it is not possible to share a public link to a recipe.
## FAQ
### When should I use keywords and when should I use categories to organize my recipes?
The use of keywords and categories is entirely up to you.
The primary difference between the two is that a recipe can only have a single category,
but may have many keywords.
In other words,
categories are a 1:N relationship while keywords are an N:M relationship.
Categories can be accessed more directly than keywords,
as they appear in the sidebar.
By clicking a category in the sidebar,
you can quickly narrow down recipes to a certain class, like "Main dishes" or "Deserts".
Then, keywords can be used to further narrow down the selection
with tags like "vegetarian" or "easy".
In this way, categories are used for rough filtering and keywords are used for fine filtering.
![Example workflow using categories for rough filtering and keywords for fine filtering](assets/keywords-and-categories.png)

Просмотреть файл

@ -58,6 +58,7 @@ OC.L10N.register(
"Could not set recipe update interval to {interval}" : "Kunne ikke sætte opdaterings interval til {interval}.",
"Could not set recipe folder to {path}" : "Kunne ikke sætte opskrifts mappen til {path}.",
"Loading config failed" : "Indlæsning af konfiguration fejlet!",
"Enter URL or select from your Nextcloud instance on the right" : "Indtast URL eller vælg fra din Nextcloud-instans til højre",
"Pick a local image" : "Vælg et lokalt billede",
"Path to your recipe image" : "Sti til dit opskriftsbillede",
"Move entry up" : "Flyt op",
@ -71,6 +72,9 @@ OC.L10N.register(
"Description" : "Beskrivelse",
"URL" : "Url",
"Image" : "Billede",
"Preparation time (hours:minutes)" : "Forberedelsestid (timer:minutter)",
"Cooking time (hours:minutes)" : "Tilberedningstid (timer:minutter)",
"Total time (hours:minutes)" : "Samlet tid (timer:minutter)",
"Choose category" : "Vælg kategori",
"Keywords" : "Nøgleord",
"Choose keywords" : "Vælg nøgleord",
@ -114,6 +118,9 @@ OC.L10N.register(
"Search recipes with this keyword" : "Søg efter opskrifter med dette nøgleord",
"Date created" : "Oprettet den",
"Last modified" : "Sidst ændret",
"Preparation time (H:MM)" : "Forberedelsestid (T:MM)",
"Cooking time (H:MM)" : "Tilberedningstid (T:MM)",
"Total time (H:MM)" : "Samlet tid (T:MM)",
"Serving Size" : "Antal portioner",
"Energy" : "Energiindhold",
"Sugar" : "Sukker",

Просмотреть файл

@ -56,6 +56,7 @@
"Could not set recipe update interval to {interval}" : "Kunne ikke sætte opdaterings interval til {interval}.",
"Could not set recipe folder to {path}" : "Kunne ikke sætte opskrifts mappen til {path}.",
"Loading config failed" : "Indlæsning af konfiguration fejlet!",
"Enter URL or select from your Nextcloud instance on the right" : "Indtast URL eller vælg fra din Nextcloud-instans til højre",
"Pick a local image" : "Vælg et lokalt billede",
"Path to your recipe image" : "Sti til dit opskriftsbillede",
"Move entry up" : "Flyt op",
@ -69,6 +70,9 @@
"Description" : "Beskrivelse",
"URL" : "Url",
"Image" : "Billede",
"Preparation time (hours:minutes)" : "Forberedelsestid (timer:minutter)",
"Cooking time (hours:minutes)" : "Tilberedningstid (timer:minutter)",
"Total time (hours:minutes)" : "Samlet tid (timer:minutter)",
"Choose category" : "Vælg kategori",
"Keywords" : "Nøgleord",
"Choose keywords" : "Vælg nøgleord",
@ -112,6 +116,9 @@
"Search recipes with this keyword" : "Søg efter opskrifter med dette nøgleord",
"Date created" : "Oprettet den",
"Last modified" : "Sidst ændret",
"Preparation time (H:MM)" : "Forberedelsestid (T:MM)",
"Cooking time (H:MM)" : "Tilberedningstid (T:MM)",
"Total time (H:MM)" : "Samlet tid (T:MM)",
"Serving Size" : "Antal portioner",
"Energy" : "Energiindhold",
"Sugar" : "Sukker",

Просмотреть файл

@ -2,6 +2,7 @@ OC.L10N.register(
"cookbook",
{
"Cannot detect type of transmitted data. This is a bug, please report it." : "Δεν είναι δυνατή η ανίχνευση τύπου μεταδιδόμενων δεδομένων. Αυτό είναι ένα σφάλμα, αναφέρετέ το.",
"Invalid URL-encoded string found. Please report a bug." : "Βρέθηκε μη-έγκυρη ακολουθία χαρακτήρων κωδικοποιημένου-υπερσυνδέσμου. Παρακαλώ αναφέρετε το σφάλμα.",
"Recipes" : "Συνταγές",
"Another recipe with that name already exists" : "Υπάρχει άλλη συνταγή με το ίδιο όνομα",
"Cookbook" : "Βιβλίο συνταγών",

Просмотреть файл

@ -1,5 +1,6 @@
{ "translations": {
"Cannot detect type of transmitted data. This is a bug, please report it." : "Δεν είναι δυνατή η ανίχνευση τύπου μεταδιδόμενων δεδομένων. Αυτό είναι ένα σφάλμα, αναφέρετέ το.",
"Invalid URL-encoded string found. Please report a bug." : "Βρέθηκε μη-έγκυρη ακολουθία χαρακτήρων κωδικοποιημένου-υπερσυνδέσμου. Παρακαλώ αναφέρετε το σφάλμα.",
"Recipes" : "Συνταγές",
"Another recipe with that name already exists" : "Υπάρχει άλλη συνταγή με το ίδιο όνομα",
"Cookbook" : "Βιβλίο συνταγών",

Просмотреть файл

@ -58,6 +58,7 @@ OC.L10N.register(
"Could not set recipe update interval to {interval}" : "No se puede ajustar el intervalo de actualización de la receta a {interval}",
"Could not set recipe folder to {path}" : "No fue posible establecer {path} como ruta para la carpeta de recetas",
"Loading config failed" : "Fallo al cargar la configuración",
"Enter URL or select from your Nextcloud instance on the right" : "Introduce la URL o selecciona desde tu instancia de Nextcloud a la derecha",
"Pick a local image" : "Seleccione una imagen local",
"Path to your recipe image" : "Ruta a tu imagen de la receta",
"Move entry up" : "Mover entrada hacia arriba",
@ -71,6 +72,9 @@ OC.L10N.register(
"Description" : "Descripción",
"URL" : "URL",
"Image" : "Imagen",
"Preparation time (hours:minutes)" : "Tiempo de preparación (horas:minutos)",
"Cooking time (hours:minutes)" : "Tiempo de cocinado (horas:minutos)",
"Total time (hours:minutes)" : "Tiempo total (horas:minutos)",
"Choose category" : "Escoge una categoría",
"Keywords" : "Palabras clave",
"Choose keywords" : "Escoge palabras clave",
@ -114,6 +118,9 @@ OC.L10N.register(
"Search recipes with this keyword" : "Buscar recetas con esta palabra clave",
"Date created" : "Fecha de creación",
"Last modified" : "Última modificación",
"Preparation time (H:MM)" : "Tiempo de preparación (H:MM)",
"Cooking time (H:MM)" : "Tiempo de cocinado (H:MM)",
"Total time (H:MM)" : "Tiempo total (H:MM)",
"Serving Size" : "Tamaño de la porción",
"Energy" : "Energía",
"Sugar" : "Azúcar",

Просмотреть файл

@ -56,6 +56,7 @@
"Could not set recipe update interval to {interval}" : "No se puede ajustar el intervalo de actualización de la receta a {interval}",
"Could not set recipe folder to {path}" : "No fue posible establecer {path} como ruta para la carpeta de recetas",
"Loading config failed" : "Fallo al cargar la configuración",
"Enter URL or select from your Nextcloud instance on the right" : "Introduce la URL o selecciona desde tu instancia de Nextcloud a la derecha",
"Pick a local image" : "Seleccione una imagen local",
"Path to your recipe image" : "Ruta a tu imagen de la receta",
"Move entry up" : "Mover entrada hacia arriba",
@ -69,6 +70,9 @@
"Description" : "Descripción",
"URL" : "URL",
"Image" : "Imagen",
"Preparation time (hours:minutes)" : "Tiempo de preparación (horas:minutos)",
"Cooking time (hours:minutes)" : "Tiempo de cocinado (horas:minutos)",
"Total time (hours:minutes)" : "Tiempo total (horas:minutos)",
"Choose category" : "Escoge una categoría",
"Keywords" : "Palabras clave",
"Choose keywords" : "Escoge palabras clave",
@ -112,6 +116,9 @@
"Search recipes with this keyword" : "Buscar recetas con esta palabra clave",
"Date created" : "Fecha de creación",
"Last modified" : "Última modificación",
"Preparation time (H:MM)" : "Tiempo de preparación (H:MM)",
"Cooking time (H:MM)" : "Tiempo de cocinado (H:MM)",
"Total time (H:MM)" : "Tiempo total (H:MM)",
"Serving Size" : "Tamaño de la porción",
"Energy" : "Energía",
"Sugar" : "Azúcar",

Просмотреть файл

@ -71,6 +71,9 @@ OC.L10N.register(
"Description" : "Description",
"URL" : "URL",
"Image" : "Image",
"Preparation time (hours:minutes)" : "Durée de préparation (heures:minutes)",
"Cooking time (hours:minutes)" : "Durée de cuisson (heures:minutes)",
"Total time (hours:minutes)" : "Durée totale (heures:minutes)",
"Choose category" : "Sélectionnez une catégorie",
"Keywords" : "Mots-clefs",
"Choose keywords" : "Sélectionner des mots-clefs",
@ -114,6 +117,9 @@ OC.L10N.register(
"Search recipes with this keyword" : "Rechercher des recettes avec ce mot-clef",
"Date created" : "Date de création",
"Last modified" : "Dernière modification",
"Preparation time (H:MM)" : "Durée de préparation (H:MM)",
"Cooking time (H:MM)" : "Durée de cuisson (H:MM)",
"Total time (H:MM)" : "Durée totale (H:MM)",
"Serving Size" : "Taille des portions",
"Energy" : "Energie",
"Sugar" : "Sucre",

Просмотреть файл

@ -69,6 +69,9 @@
"Description" : "Description",
"URL" : "URL",
"Image" : "Image",
"Preparation time (hours:minutes)" : "Durée de préparation (heures:minutes)",
"Cooking time (hours:minutes)" : "Durée de cuisson (heures:minutes)",
"Total time (hours:minutes)" : "Durée totale (heures:minutes)",
"Choose category" : "Sélectionnez une catégorie",
"Keywords" : "Mots-clefs",
"Choose keywords" : "Sélectionner des mots-clefs",
@ -112,6 +115,9 @@
"Search recipes with this keyword" : "Rechercher des recettes avec ce mot-clef",
"Date created" : "Date de création",
"Last modified" : "Dernière modification",
"Preparation time (H:MM)" : "Durée de préparation (H:MM)",
"Cooking time (H:MM)" : "Durée de cuisson (H:MM)",
"Total time (H:MM)" : "Durée totale (H:MM)",
"Serving Size" : "Taille des portions",
"Energy" : "Energie",
"Sugar" : "Sucre",

Просмотреть файл

@ -58,6 +58,7 @@ OC.L10N.register(
"Could not set recipe update interval to {interval}" : "Impossibile impostare l'intervallo di aggiornamento delle ricette a {interval}",
"Could not set recipe folder to {path}" : "Impossibile impostare la cartella delle ricette a {path}",
"Loading config failed" : "Caricamento della configurazione non riuscito",
"Enter URL or select from your Nextcloud instance on the right" : "Inserisci l'URL o seleziona dalla tua istanza Nextcloud sulla destra",
"Pick a local image" : "Scegli un'immagine locale",
"Path to your recipe image" : "Percorso all'immagine della ricetta",
"Move entry up" : "Sposta voce su",
@ -71,6 +72,9 @@ OC.L10N.register(
"Description" : "Descrizione",
"URL" : "URL",
"Image" : "Immagine",
"Preparation time (hours:minutes)" : "Tempo di preparazione (ore:minuti):",
"Cooking time (hours:minutes)" : "Tempo di cottura (ore:minuti):",
"Total time (hours:minutes)" : "Tempo totale (ore:minuti):",
"Choose category" : "Scegli la categoria",
"Keywords" : "Parole chiave",
"Choose keywords" : "Scegli le parole chiave",
@ -114,6 +118,9 @@ OC.L10N.register(
"Search recipes with this keyword" : "Cerca ricette con questa parola chiave",
"Date created" : "Data di creazione",
"Last modified" : "Ultima modifica",
"Preparation time (H:MM)" : "Tempo di preparazione (H:MM)",
"Cooking time (H:MM)" : "Tempo di cottura (H:MM)",
"Total time (H:MM)" : "Tempo totale (H:MM)",
"Serving Size" : "Dimensioni della porzione",
"Energy" : "Energia",
"Sugar" : "Zuccheri",

Просмотреть файл

@ -56,6 +56,7 @@
"Could not set recipe update interval to {interval}" : "Impossibile impostare l'intervallo di aggiornamento delle ricette a {interval}",
"Could not set recipe folder to {path}" : "Impossibile impostare la cartella delle ricette a {path}",
"Loading config failed" : "Caricamento della configurazione non riuscito",
"Enter URL or select from your Nextcloud instance on the right" : "Inserisci l'URL o seleziona dalla tua istanza Nextcloud sulla destra",
"Pick a local image" : "Scegli un'immagine locale",
"Path to your recipe image" : "Percorso all'immagine della ricetta",
"Move entry up" : "Sposta voce su",
@ -69,6 +70,9 @@
"Description" : "Descrizione",
"URL" : "URL",
"Image" : "Immagine",
"Preparation time (hours:minutes)" : "Tempo di preparazione (ore:minuti):",
"Cooking time (hours:minutes)" : "Tempo di cottura (ore:minuti):",
"Total time (hours:minutes)" : "Tempo totale (ore:minuti):",
"Choose category" : "Scegli la categoria",
"Keywords" : "Parole chiave",
"Choose keywords" : "Scegli le parole chiave",
@ -112,6 +116,9 @@
"Search recipes with this keyword" : "Cerca ricette con questa parola chiave",
"Date created" : "Data di creazione",
"Last modified" : "Ultima modifica",
"Preparation time (H:MM)" : "Tempo di preparazione (H:MM)",
"Cooking time (H:MM)" : "Tempo di cottura (H:MM)",
"Total time (H:MM)" : "Tempo totale (H:MM)",
"Serving Size" : "Dimensioni della porzione",
"Energy" : "Energia",
"Sugar" : "Zuccheri",

Просмотреть файл

@ -72,7 +72,7 @@ class MainController extends Controller {
*/
public function getApiVersion(): DataResponse {
$response = [
'cookbook_version' => [0, 9, 9], /* VERSION_TAG do not change this line manually */
'cookbook_version' => [0, 9, 10], /* VERSION_TAG do not change this line manually */
'api_version' => [
'epoch' => 0,
'major' => 0,

Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "nextcloud-cookbook",
"version": "0.9.9",
"version": "0.9.10",
"description": "",
"main": "src/main.js",
"scripts": {
@ -30,7 +30,7 @@
"@nextcloud/axios": "^1.6.0",
"@nextcloud/moment": "^1.1.1",
"@nextcloud/router": "^2.0.0",
"@nextcloud/vue": "^4.0.2",
"@nextcloud/vue": "^5.1.0",
"linkifyjs": "^3.0.1",
"lozad": "^1.16.0",
"node-sass": "^7.0.0",

Просмотреть файл

@ -344,8 +344,19 @@ export default {
<style scoped>
.wrapper {
/* Sticky is better than fixed because fixed takes the element out of flow,
which breaks the height, putting elements underneath */
position: sticky;
/* This is competing with the recipe instructions which have z-index: 1 */
z-index: 2;
/* The height of the nextcloud header */
top: var(--header-height);
width: 100%;
padding-left: 4px;
border-bottom: 1px solid var(--color-border);
background-color: var(--color-main-background);
}
.active {

Просмотреть файл

@ -15,7 +15,7 @@
ref="list-field"
v-model="buffer[idx]"
type="text"
@keyup="keyPressed"
@keydown="keyDown"
@input="handleInput"
@paste="handlePaste"
/>
@ -23,7 +23,7 @@
v-else-if="fieldType === 'textarea'"
ref="list-field"
v-model="buffer[idx]"
@keyup="keyPressed"
@keydown="keyDown"
@input="handleInput"
@paste="handlePaste"
></textarea>
@ -58,6 +58,26 @@
</template>
<script>
const linesMatchAtPosition = (lines, i) =>
lines.every((line) => line[i] === lines[0][i])
const findCommonPrefix = (lines) => {
// Find the substring common to the array of strings
// Inspired from https://stackoverflow.com/questions/68702774/longest-common-prefix-in-javascript
// Check border cases size 1 array and empty first word)
if (!lines[0] || lines.length === 1) return lines[0] || ""
// Loop up index until the characters do not match
for (let i = 0; ; i++) {
// If first line has fewer than i characters
// or the character of each line at position i is not identical
if (!lines[0][i] || !linesMatchAtPosition(lines, i)) {
// Then the desired prefix is the substring from the beginning to i
return lines[0].substr(0, i)
}
}
}
export default {
name: "EditInputGroup",
props: {
@ -95,8 +115,6 @@ export default {
return {
// helper variables
buffer: this.value.slice(),
contentPasted: false,
singleLinePasted: false,
lastFocusedFieldIndex: null,
lastCursorPosition: -1,
ignoreNextKeyUp: false,
@ -142,41 +160,43 @@ export default {
/**
* Handle typing in input or field or textarea
*/
handleInput() {
// wait a tick to check if content was typed or pasted
this.$nextTick(function handlePastedOrTyped() {
if (this.contentPasted) {
this.contentPasted = false
if (this.singleLinePasted) {
this.$emit("input", this.buffer)
}
return
}
this.$emit("input", this.buffer)
})
handleInput(e) {
// Exit early if input was pasted. Let `handlePaste` handle this.
// References:
// https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType
// https://rawgit.com/w3c/input-events/v1/index.html#interface-InputEvent-Attributes
if (
e.inputType === "insertFromPaste" ||
e.inputType === "insertFromPasteAsQuotation"
) {
return
}
this.$emit("input", this.buffer)
},
/**
* Handle paste in input field or textarea
*/
handlePaste(e) {
this.contentPasted = true
if (!this.createFieldsOnNewlines) {
return
}
// get data from clipboard to keep newline characters, which are stripped
// from the data pasted in the input field (e.target.value)
const clipboardData = e.clipboardData || window.clipboardData
const pastedData = clipboardData.getData("Text")
const inputLinesArray = pastedData.split(/\r\n|\r|\n/g)
const inputLinesArray = pastedData
.split(/\r\n|\r|\n/g)
// Remove empty lines
.filter((line) => line.trim() !== "")
// If only a single line pasted, emit that line and exit
// Treat it as if that single line was typed
if (inputLinesArray.length === 1) {
this.singleLinePasted = true
this.$emit("input", this.buffer)
return
}
// From here on, multiple lines pasted
if (!this.createFieldsOnNewlines) {
return
}
this.singleLinePasted = false
e.preventDefault()
@ -187,12 +207,25 @@ export default {
$li
)
// Remove empty lines
for (let i = inputLinesArray.length - 1; i >= 0; --i) {
if (inputLinesArray[i].trim() === "") {
inputLinesArray.splice(i, 1)
}
// Remove the common prefix from each line of the pasted text
// For example, if the pasted text uses - for a bullet list
const prefix = findCommonPrefix(inputLinesArray)
// Inspired from https://stackoverflow.com/a/25575009
// Ensure that we are only removing common punctuation
// For example, if many lines start with the same word, keep that
// This is more robust than filtering our [a-zA-Z] in the prefix
// as it should work for any alphabet
const re =
/[^\s\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-./:;<=>?@[\]^_`{|}~]/g
const prefixLength = re.test(prefix)
? prefix.search(re)
: prefix.length
for (let i = 0; i < inputLinesArray.length; ++i) {
inputLinesArray[i] = inputLinesArray[i].slice(prefixLength)
}
for (let i = 0; i < inputLinesArray.length; ++i) {
this.addNewEntry(
$insertedIndex + i + 1,
@ -210,13 +243,12 @@ export default {
indexToFocus -= 1
}
this.$refs["list-field"][indexToFocus].focus()
this.contentPasted = false
})
},
/**
* Catches enter and key down presses and either adds a new row or focuses the one below
*/
keyPressed(e) {
keyDown(e) {
// If, e.g., enter has been pressed in the multiselect popup to select an option,
// ignore the following keyup event
if (this.ignoreNextKeyUp) {
@ -225,58 +257,69 @@ export default {
}
// Allow new lines with shift key
if ((e.keyCode === 13 || e.keyCode === 10) && e.shiftKey) {
if (e.key === "Enter" && e.shiftKey) {
// Do nothing here, user wants a line break
return
}
// Using keyup for trigger will prevent repeat triggering if key is held down
// Repeat events should be ignored
if (e.repeat) {
return
}
// Only do anything for enter or # keys
if (
e.keyCode === 13 ||
e.keyCode === 10 ||
(this.referencePopupEnabled && e.key === "#")
e.key !== "Enter" &&
!(this.referencePopupEnabled && e.key === "#")
) {
return
}
// Get the index of the pressed list item
const $li = e.currentTarget.closest("li")
const $ul = $li.closest("ul")
const $pressedLiIndex = Array.prototype.indexOf.call(
$ul.childNodes,
$li
)
if (e.key === "Enter") {
e.preventDefault()
const $li = e.currentTarget.closest("li")
const $ul = $li.closest("ul")
const $pressedLiIndex = Array.prototype.indexOf.call(
$ul.childNodes,
$li
)
if (e.keyCode === 13 || e.keyCode === 10) {
if (
$pressedLiIndex >=
this.$refs["list-field"].length - 1
) {
this.addNewEntry()
} else {
$ul.children[$pressedLiIndex + 1]
.getElementsByTagName("input")[0]
.focus()
}
} else if (this.referencePopupEnabled && e.key === "#") {
if ($pressedLiIndex >= this.$refs["list-field"].length - 1) {
this.addNewEntry()
} else {
// Focus the next input or textarea
// We have to check for both, as inputs are used for
// ingredients and textareas are used for instructions
$ul.children[$pressedLiIndex + 1]
.querySelector("input, textarea")
.focus()
}
}
if (this.referencePopupEnabled && e.key === "#") {
const elm = this.$refs["list-field"][$pressedLiIndex]
// Check if the letter before the hash
// This is a keydown event listener, so the `#` does not
// exist in the input yet
// `cursorPos` will be the index in the textfield before the #
// was pressed
const cursorPos = elm.selectionStart
const content = elm.value
// Show the popup only if the # was inserted at the very
// beggining of the input or after any whitespace character
if (
cursorPos === 0 ||
/\s/.test(content.charAt(cursorPos - 1))
) {
e.preventDefault()
const elm = this.$refs["list-field"][$pressedLiIndex]
// Check if the letter before the hash
const cursorPos = elm.selectionStart
const content = elm.value
const prevChar =
cursorPos > 1 ? content.charAt(cursorPos - 2) : ""
if (
cursorPos === 1 ||
prevChar === " " ||
prevChar === "\n" ||
prevChar === "\r"
) {
// Show dialog to select recipe
this.$parent.$emit("showRecipeReferencesPopup", {
context: this,
})
this.lastFocusedFieldIndex = $pressedLiIndex
this.lastCursorPosition = cursorPos
}
// Show dialog to select recipe
this.$parent.$emit("showRecipeReferencesPopup", {
context: this,
})
this.lastFocusedFieldIndex = $pressedLiIndex
this.lastCursorPosition = cursorPos
}
}
},

Просмотреть файл

@ -667,7 +667,14 @@ export default {
paddedTime: this.recipe.totalTime,
}
this.selectedKeywords = this.recipe.keywords.split(",")
this.selectedKeywords = this.recipe.keywords
.split(",")
.map((kw) => kw.trim())
// Remove any empty keywords
// If the response from the server is just an empty
// string, split will create an array of a single empty
// string
.filter((kw) => kw !== "")
// fallback if fetching all keywords fails
this.selectedKeywords.forEach((kw) => {