зеркало из https://github.com/nextcloud/cookbook.git
Merge remote-tracking branch 'upstream/master' into document-development-environment
Signed-off-by: Christian Wolf <github@christianwolf.email>
This commit is contained in:
Коммит
98f44587b6
|
@ -1 +1 @@
|
|||
9
|
||||
10
|
||||
|
|
27
CHANGELOG.md
27
CHANGELOG.md
|
@ -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
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 52 KiB |
Двоичный файл не отображается.
|
@ -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) => {
|
||||
|
|
Загрузка…
Ссылка в новой задаче