1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-04 04:55:21 +02:00

Release v0.1.0 Candidate (#85)

* Changed uvicorn port to 80

* Changed port in docker-compose to match dockerfile

* Readded environment variables in docker-compose

* production image rework

* Use opengraph metadata to make basic recipe cards when full recipe metadata is not available

* fixed instrucitons on parse

* add last_recipe

* automated testing

* roadmap update

* Sqlite (#75)

* file structure

* auto-test

* take 2

* refactor ap scheduler and startup process

* fixed scraper error

* database abstraction

* database abstraction

* port recipes over to new schema

* meal migration

* start settings migration

* finale mongo port

* backup improvements

* migration imports to new DB structure

* unused import cleanup

* docs strings

* settings and theme import logic

* cleanup

* fixed tinydb error

* requirements

* fuzzy search

* remove scratch file

* sqlalchemy models

* improved search ui

* recipe models almost done

* sql modal population

* del scratch

* rewrite database model mixins

* mostly grabage

* recipe updates

* working sqllite

* remove old files and reorganize

* final cleanup

Co-authored-by: Hayden <hay-kot@pm.me>

* Backup card (#78)

* backup / import dialog

* upgrade to new tag method

* New import card

* rename settings.py to app_config.py

* migrate to poetry for development

* fix failing test

Co-authored-by: Hayden <hay-kot@pm.me>

* added mkdocs to docker-compose

* Translations (#72)

* Translations + danish

* changed back proxy target to use ENV

* Resolved more merge conflicts

* Removed test in translation

* Documentation of translations

* Updated translations

* removed old packages

Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>

* fail to start bug fixes

* feature: prep/cook/total time slots (#80)

Co-authored-by: Hayden <hay-kot@pm.me>

* missing bind attributes

* Bug fixes (#81)

* fix: url remains after succesful import

* docs: changelog + update todos

* arm image

* arm compose

* compose updates

* update poetry

* arm support

Co-authored-by: Hayden <hay-kot@pm.me>

* dockerfile hotfix

* dockerfile hotfix

* Version Release Final Touches (#84)

* Remove slim

* bug: opacity issues

* bug: startup failure with no database

* ci/cd on dev branch

* formatting

* v0.1.0 documentation

Co-authored-by: Hayden <hay-kot@pm.me>

* db init hotfix

* bug: fix crash in mongo

* fix mongo bug

* fixed version notifier

* finale changelog

Co-authored-by: kentora <=>
Co-authored-by: Hayden <hay-kot@pm.me>
Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com>
Co-authored-by: kentora <kentora@kentora.dk>
This commit is contained in:
Hayden 2021-01-17 22:22:54 -09:00 committed by GitHub
parent f6c1fa0e8b
commit 88dfd40b8d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
173 changed files with 10273 additions and 3735 deletions

View file

@ -1,12 +1,13 @@
<template>
<v-card>
<v-card-title class="headline"> Edit Meal Plan </v-card-title>
<v-card-title class="headline"> {{$t('meal-plan.edit-meal-plan')}} </v-card-title>
<v-divider></v-divider>
<v-card-text>
<MealPlanCard v-model="mealPlan.meals" />
<v-row align="center" justify="end">
<v-card-actions>
<v-btn color="success" text @click="update"> Update </v-btn>
<v-btn color="success" text @click="update"> {{$t('general.update')}} </v-btn>
<v-spacer></v-spacer>
</v-card-actions>
</v-row>

View file

@ -1,6 +1,8 @@
<template>
<v-card>
<v-card-title class="headline"> Create a New Meal Plan </v-card-title>
<v-card-title class="headline">
{{$t('meal-plan.create-a-new-meal-plan')}}
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<v-row dense>
@ -17,7 +19,7 @@
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-model="startComputedDateFormatted"
label="Start Date"
:label="$t('meal-plan.start-date')"
persistent-hint
prepend-icon="mdi-calendar"
readonly
@ -45,7 +47,7 @@
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-model="endComputedDateFormatted"
label="End Date"
:label="$t('meal-plan.end-date')"
persistent-hint
prepend-icon="mdi-calendar"
readonly
@ -69,9 +71,9 @@
<v-row align="center" justify="end">
<v-card-actions>
<v-btn color="success" @click="random" v-if="meals[1]" text>
Random
{{$t('general.random')}}
</v-btn>
<v-btn color="success" @click="save" text> Save </v-btn>
<v-btn color="success" @click="save" text> {{$t('general.save')}} </v-btn>
<v-spacer></v-spacer>
<v-btn icon @click="show = !show"> </v-btn>

View file

@ -2,7 +2,7 @@
<v-row justify="center">
<v-dialog v-model="dialog" persistent max-width="800">
<v-card>
<v-card-title class="headline"> Choose a Recipe </v-card-title>
<v-card-title class="headline"> {{$t('meal-plan.choose-a-recipe')}} </v-card-title>
<v-card-text>
<v-autocomplete
:items="availableRecipes"
@ -13,14 +13,12 @@
hide-details
hide-selected
item-text="slug"
label="Search for a Recipe"
:label="$t('search.search-for-a-recipe')"
single-line
>
<template v-slot:no-data>
<v-list-item>
<v-list-item-title>
Search for your Favorite
<strong>Recipe</strong>
<v-list-item-title :v-html="$t('search.search-for-your-favorite-recipe')">
</v-list-item-title>
</v-list-item>
</template>
@ -44,8 +42,8 @@
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="secondary" text @click="dialog = false"> Close </v-btn>
<v-btn color="secondary" text @click="dialog = false"> Select </v-btn>
<v-btn color="secondary" text @click="dialog = false"> {{$t('general.close')}} </v-btn>
<v-btn color="secondary" text @click="dialog = false"> {{$t('general.select')}} </v-btn>
</v-card-actions>
</v-card>
</v-dialog>

View file

@ -10,17 +10,16 @@
v-on="on"
@click="inputText = ''"
>
Bulk Add
{{$t('new-recipe.bulk-add')}}
</v-btn>
</template>
<v-card>
<v-card-title class="headline"> Bulk Add </v-card-title>
<v-card-title class="headline"> {{$t('new-recipe.bulk-add')}} </v-card-title>
<v-card-text>
<p>
Paste in your recipe data. Each line will be treated as an item in a
list
{{$t('new-recipe.paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list')}}
</p>
<v-textarea v-model="inputText"> </v-textarea>
</v-card-text>
@ -29,7 +28,7 @@
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="success" text @click="save"> Save </v-btn>
<v-btn color="success" text @click="save"> {{$t('general.save')}} </v-btn>
</v-card-actions>
</v-card>
</v-dialog>

View file

@ -6,21 +6,41 @@
<v-col>
<v-file-input
v-model="fileObject"
label="Image File"
:label="$t('general.image-file')"
truncate-length="30"
@change="uploadImage"
></v-file-input>
</v-col>
<v-col cols="3"></v-col>
<v-row>
<v-col>
<v-text-field
label="Total Time"
v-model="value.totalTime"
></v-text-field>
</v-col>
<v-col
><v-text-field
label="Prep Time"
v-model="value.prepTime"
></v-text-field
></v-col>
<v-col
><v-text-field
label="Cook Time / Perform Time"
v-model="value.performTime"
></v-text-field
></v-col>
</v-row>
</v-row>
<v-text-field class="my-3" label="Recipe Name" v-model="value.name">
<v-text-field class="my-3" :label="$t('recipe.recipe-name')" v-model="value.name">
</v-text-field>
<v-textarea height="100" label="Description" v-model="value.description">
<v-textarea height="100" :label="$t('recipe.description')" v-model="value.description">
</v-textarea>
<div class="my-2"></div>
<v-row dense disabled>
<v-col sm="5">
<v-text-field label="Servings" v-model="value.recipeYield">
<v-text-field :label="$t('recipe.servings')" v-model="value.recipeYield">
</v-text-field>
</v-col>
<v-col></v-col>
@ -34,7 +54,7 @@
</v-row>
<v-row>
<v-col cols="12" sm="12" md="4" lg="4">
<h2 class="mb-4">Ingredients</h2>
<h2 class="mb-4">{{$t('recipe.ingredients')}}</h2>
<div
v-for="(ingredient, index) in value.recipeIngredient"
:key="generateKey('ingredient', index)"
@ -51,7 +71,7 @@
<v-icon color="error">mdi-delete</v-icon>
</v-btn>
<v-text-field
label="Ingredient"
:label="$t('recipe.ingredient')"
v-model="value.recipeIngredient[index]"
></v-text-field>
</v-row>
@ -61,7 +81,7 @@
</v-btn>
<BulkAdd @bulk-data="appendIngredients" />
<h2 class="mt-6">Categories</h2>
<h2 class="mt-6">{{$t('recipe.categories')}}</h2>
<v-combobox
dense
multiple
@ -83,7 +103,7 @@
</template>
</v-combobox>
<h2 class="mt-4">Tags</h2>
<h2 class="mt-4">{{$t('recipe.tags')}}</h2>
<v-combobox dense multiple chips deletable-chips v-model="value.tags">
<template v-slot:selection="data">
<v-chip
@ -98,7 +118,7 @@
</template>
</v-combobox>
<h2 class="my-4">Notes</h2>
<h2 class="my-4">{{$t('recipe.notes')}}</h2>
<v-card
class="mt-1"
v-for="(note, index) in value.notes"
@ -122,7 +142,7 @@
></v-text-field>
</v-row>
<v-textarea label="Note" v-model="value.notes[index]['text']">
<v-textarea :label="$t('recipe.note')" v-model="value.notes[index]['text']">
</v-textarea>
</v-card-text>
</v-card>
@ -135,7 +155,7 @@
<v-divider class="my-divider" :vertical="true"></v-divider>
<v-col cols="12" sm="12" md="8" lg="8">
<h2 class="mb-4">Instructions</h2>
<h2 class="mb-4">{{$t('recipe.instructions')}}</h2>
<div v-for="(step, index) in value.recipeInstructions" :key="index">
<v-hover v-slot="{ hover }">
<v-card
@ -153,7 +173,7 @@
@click="removeStep(index)"
>
<v-icon color="error">mdi-delete</v-icon> </v-btn
>Step: {{ index + 1 }}</v-card-title
>{{ $t('recipe.step-index', {step: index + 1}) }}</v-card-title
>
<v-card-text>
<v-textarea

View file

@ -0,0 +1,107 @@
<template>
<v-card
color="accent"
class="custom-transparent"
tile
:width="`${timeCardWidth}`"
>
<v-card-text
class="text-caption white--text"
v-if="totalTime || prepTime || performTime"
>
<v-row align="center" dense>
<v-col :cols="iconColumn">
<v-icon large color="white"> mdi-clock-outline </v-icon>
</v-col>
<v-divider
vertical
color="white"
class="my-1"
v-if="totalTime"
></v-divider>
<v-col v-if="totalTime">
<div><strong> Total Time </strong></div>
<div>{{ totalTime }}</div>
</v-col>
<v-divider
vertical
color="white"
class="my-1"
v-if="prepTime"
></v-divider>
<v-col v-if="prepTime">
<div><strong> Prep Time </strong></div>
<div>{{ prepTime }}</div>
</v-col>
<v-divider
vertical
color="white"
class="my-1"
v-if="performTime"
></v-divider>
<v-col v-if="performTime">
<div><strong> Cook Time </strong></div>
<div>{{ performTime }}</div>
</v-col>
</v-row>
</v-card-text>
</v-card>
</template>
<script>
export default {
props: {
prepTime: String,
totalTime: String,
performTime: String,
},
computed: {
timeLength() {
let times = [];
let timeArray = [this.totalTime, this.prepTime, this.performTime];
timeArray.forEach((element) => {
if (element) {
times.push(element);
}
});
return times.length;
},
iconColumn() {
switch (this.timeLength) {
case 0:
return null;
case 1:
return 4;
case 2:
return 3;
case 3:
return 2;
default:
return 1;
}
},
timeCardWidth() {
let timeArray = [this.totalTime, this.prepTime, this.performTime];
let width = 120;
timeArray.forEach((element) => {
if (element) {
width += 70;
}
});
if (this.$vuetify.breakpoint.name === "xs") {
return "100%";
}
return `${width}px`;
},
},
};
</script>
<style scoped>
.custom-transparent {
opacity: 0.7;
}
</style>

View file

@ -32,7 +32,7 @@
</v-row>
<v-row>
<v-col cols="12" sm="12" md="4" lg="4">
<h2 class="mb-4">Ingredients</h2>
<h2 class="mb-4">{{$t('recipe.ingredients')}}</h2>
<div
v-for="(ingredient, index) in ingredients"
:key="generateKey('ingredient', index)"
@ -47,7 +47,7 @@
</div>
<div v-if="categories[0]">
<h2 class="mt-4">Categories</h2>
<h2 class="mt-4">{{$t('recipe.categories')}}</h2>
<v-chip
class="ma-1"
color="accent"
@ -60,7 +60,7 @@
</div>
<div v-if="tags[0]">
<h2 class="mt-4">Tags</h2>
<h2 class="mt-4">{{$t('recipe.tags')}}</h2>
<v-chip
class="ma-1"
color="accent"
@ -72,7 +72,7 @@
</v-chip>
</div>
<h2 v-if="notes[0]" class="my-4">Notes</h2>
<h2 v-if="notes[0]" class="my-4">{{$t('recipe.notes')}}</h2>
<v-card
class="mt-1"
v-for="(note, index) in notes"
@ -87,7 +87,7 @@
<v-divider class="my-divider" :vertical="true"></v-divider>
<v-col cols="12" sm="12" md="8" lg="8">
<h2 class="mb-4">Instructions</h2>
<h2 class="mb-4">{{$t('recipe.instructions')}}</h2>
<v-hover
v-for="(step, index) in instructions"
:key="generateKey('step', index)"
@ -99,7 +99,7 @@
:elevation="hover ? 12 : 2"
@click="toggleDisabled(index)"
>
<v-card-title>Step: {{ index + 1 }}</v-card-title>
<v-card-title>{{ $t('recipe.step-index', {step: index + 1}) }}</v-card-title>
<v-card-text>{{ step.text }}</v-card-text>
</v-card>
</v-hover>
@ -121,7 +121,7 @@
target="_blank"
class="rounded-sm mr-4"
>
Original Recipe
{{$t('recipe.original-recipe')}}
</v-btn>
</v-row>
</v-card-text>

View file

@ -0,0 +1,90 @@
<template>
<div>
<ImportDialog
:name="selectedName"
:date="selectedDate"
ref="import_dialog"
@import="importBackup"
@delete="deleteBackup"
/>
<v-row>
<v-col
:sm="6"
:md="6"
:lg="4"
:xl="4"
v-for="backup in backups"
:key="backup.name"
>
<v-card @click="openDialog(backup)">
<v-card-text>
<v-row align="center">
<v-col cols="12" sm="2">
<v-icon color="primary"> mdi-backup-restore </v-icon>
</v-col>
<v-col cols="12" sm="10">
<div>
<strong>{{ backup.name }}</strong>
</div>
<div>{{ readableTime(backup.date) }}</div>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
</v-row>
</div>
</template>
<script>
import ImportDialog from "./ImportDialog";
import api from "../../../api";
import utils from "../../../utils";
export default {
props: {
backups: Array,
},
components: {
ImportDialog,
},
data() {
return {
selectedName: "",
selectedDate: "",
loading: false,
};
},
methods: {
openDialog(backup) {
this.selectedDate = this.readableTime(backup.date);
this.selectedName = backup.name;
this.$refs.import_dialog.open();
},
readableTime(timestamp) {
let date = new Date(timestamp);
return utils.getDateAsText(date);
},
async importBackup(data) {
this.$emit("loading");
let response = await api.backups.import(data.name, data);
let failed = response.data.failed;
let succesful = response.data.successful;
this.$emit("finished", succesful, failed);
},
deleteBackup(data) {
this.$emit("loading");
api.backups.delete(data.name);
this.selectedBackup = null;
this.backupLoading = false;
this.$emit("finished");
},
},
};
</script>
<style>
</style>

View file

@ -0,0 +1,129 @@
<template>
<div class="text-center">
<v-dialog v-model="dialog" width="500">
<v-card>
<v-card-title> {{ name }} </v-card-title>
<v-card-subtitle class="mb-n3"> {{ date }} </v-card-subtitle>
<v-divider></v-divider>
<v-card-text>
<v-row>
<v-col>
<v-checkbox
class="mb-n4 mt-1"
dense
:label="$t('settings.backup.import-recipes')"
v-model="importRecipes"
></v-checkbox>
<v-checkbox
class="my-n4"
dense
:label="$t('settings.backup.import-themes')"
v-model="importThemes"
></v-checkbox>
<v-checkbox
class="my-n4"
dense
:label="$t('settings.backup.import-settings')"
v-model="importSettings"
></v-checkbox>
</v-col>
<!-- <v-col>
<v-tooltip top>
<template v-slot:activator="{ on, attrs }">
<span v-on="on" v-bind="attrs">
<v-checkbox
class="mb-n4 mt-1"
dense
label="Force"
v-model="forceImport"
></v-checkbox>
</span>
</template>
<span>Force update existing recipes</span>
</v-tooltip>
<v-tooltip top>
<template v-slot:activator="{ on, attrs }">
<span v-on="on" v-bind="attrs">
<v-checkbox
class="mb-n4 mt-1"
dense
label="Rebase"
v-model="rebaseImport"
></v-checkbox>
</span>
</template>
<span
>Removes all recipes, and then imports recipes from the
backup</span
>
</v-tooltip>
</v-col> -->
</v-row>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-btn disabled color="success" text @click="raiseEvent('download')">
{{$t('general.download')}}
</v-btn>
<v-spacer></v-spacer>
<v-btn color="error" text @click="raiseEvent('delete')">
{{$t('general.delete')}}
</v-btn>
<v-btn color="success" text @click="raiseEvent('import')">
{{$t('general.import')}}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
export default {
props: {
name: {
default: "Backup Name",
},
date: {
default: "Backup Date",
},
},
data() {
return {
dialog: false,
importRecipes: true,
forceImport: false,
rebaseImport: false,
importThemes: false,
importSettings: false,
};
},
methods: {
open() {
this.dialog = true;
},
close() {
this.dialog = false;
},
raiseEvent(event) {
let eventData = {
name: this.name,
recipes: this.importRecipes,
force: this.forceImport,
rebase: this.rebaseImport,
themes: this.importThemes,
settings: this.importSettings,
};
this.close();
this.$emit(event, eventData);
},
},
};
</script>
<style>
</style>

View file

@ -1,61 +1,38 @@
<template>
<v-card :loading="backupLoading">
<v-card-title class="headline"> Backup and Exports </v-card-title>
<v-card :loading="backupLoading" class="mt-3">
<v-card-title class="headline">
{{$t('settings.backup-and-exports')}}
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<p>
Backups are exported in standard JSON format along with all the images
stored on the file system. In your backup folder you'll find a .zip file
that contains all of the recipe JSON and images from the database.
Additionally, if you selected a markdown file, those will also be stored
in the .zip file. To import a backup, it must be located in your backups
folder. Automated backups are done each day at 3:00 AM.
{{$t('settings.backup-info')}}
</p>
<v-row dense align="center">
<v-col dense cols="12" sm="12" md="4">
<v-text-field v-model="backupTag" label="Backup Tag"></v-text-field>
<v-text-field v-model="backupTag" :label="$t('settings.backup-tag')"></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-combobox
auto-select-first
label="Markdown Template"
:label="$t('settings.markdown-template')"
:items="availableTemplates"
v-model="selectedTemplate"
></v-combobox>
</v-col>
<v-col dense cols="12" sm="12" md="2">
<v-btn block color="accent" @click="createBackup" width="165">
Backup Recipes
</v-btn>
</v-col>
</v-row>
<v-row dense align="center">
<v-col dense cols="12" sm="12" md="4">
<v-form ref="form">
<v-combobox
auto-select-first
label="Select a Backup for Import"
:items="availableBackups"
v-model="selectedBackup"
:rules="[(v) => !!v || 'Backup Selection is Required']"
required
></v-combobox>
</v-form>
</v-col>
<v-col dense cols="12" sm="12" md="3" lg="2">
<v-btn block color="accent" @click="importBackup">
Import Backup
</v-btn>
</v-col>
<v-col dense cols="12" sm="12" md="2" lg="2">
<v-btn block color="error" @click="deleteBackup">
Delete Backup
<v-btn block text color="accent" @click="createBackup" width="165">
{{$t('settings.backup-recipes')}}
</v-btn>
</v-col>
</v-row>
<BackupCard
@loading="backupLoading = true"
@finished="processFinished"
:backups="availableBackups"
/>
<SuccessFailureAlert
success-header="Successfully Imported"
:success="successfulImports"
@ -69,10 +46,12 @@
<script>
import api from "../../../api";
import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
import BackupCard from "./BackupCard";
export default {
components: {
SuccessFailureAlert,
BackupCard,
},
data() {
return {
@ -95,18 +74,6 @@ export default {
this.availableBackups = response.imports;
this.availableTemplates = response.templates;
},
async importBackup() {
if (this.$refs.form.validate()) {
this.backupLoading = true;
let response = await api.backups.import(this.selectedBackup);
console.log(response.data);
this.failedImports = response.data.failed;
this.successfulImports = response.data.successful;
this.backupLoading = false;
}
},
deleteBackup() {
if (this.$refs.form.validate()) {
this.backupLoading = true;
@ -121,10 +88,7 @@ export default {
async createBackup() {
this.backupLoading = true;
let response = await api.backups.create(
this.backupTag,
this.selectedTemplate
);
let response = await api.backups.create(this.backupTag, this.templates);
if (response.status == 201) {
this.selectedBackup = null;
@ -132,6 +96,12 @@ export default {
this.backupLoading = false;
}
},
processFinished(successful = null, failed = null) {
this.getAvailableBackups();
this.backupLoading = false;
this.successfulImports = successful;
this.failedImports = failed;
},
},
};
</script>

View file

@ -1,26 +1,25 @@
<template>
<v-card-text>
<p>
Currently Chowdown via public Repo URL is the only supported type of
migration
{{$t('migration.currently-chowdown-via-public-repo-url-is-the-only-supported-type-of-migration')}}
</p>
<v-form ref="form">
<v-row dense align="center">
<v-col cols="12" md="5" sm="5">
<v-text-field
v-model="repo"
label="Chowdown Repo URL"
:label="$t('migration.chowdown-repo-url')"
:rules="[rules.required]"
>
</v-text-field>
</v-col>
<v-col cols="12" md="4" sm="5">
<v-btn text color="info" @click="importRepo"> Migrate </v-btn>
<v-btn text color="info" @click="importRepo"> {{$t('migration.migrate')}} </v-btn>
</v-col>
</v-row>
</v-form>
<v-alert v-if="failedRecipes[1]" outlined dense type="error">
<h4>Failed Recipes</h4>
<h4>{{$t('migration.failed-recipes')}}</h4>
<v-list dense>
<v-list-item v-for="fail in this.failedRecipes" :key="fail">
{{ fail }}
@ -28,7 +27,7 @@
</v-list>
</v-alert>
<v-alert v-if="failedImages[1]" outlined dense type="error">
<h4>Failed Images</h4>
<h4>{{$t('migration.failed-images')}}</h4>
<v-list dense>
<v-list-item v-for="fail in this.failedImages" :key="fail">
{{ fail }}

View file

@ -1,9 +1,7 @@
<template>
<v-card-text>
<p>
You can import recipes from either a zip file or a directory located in
the /app/data/migraiton/ folder. Please review the documentation to ensure
your directory structure matches what is expected
{{$t('migration.you-can-import-recipes-from-either-a-zip-file-or-a-directory-located-in-the-app-data-migraiton-folder-please-review-the-documentation-to-ensure-your-directory-structure-matches-what-is-expected')}}
</p>
<v-form ref="form">
<v-row align="center">
@ -11,20 +9,20 @@
<v-select
:items="availableImports"
v-model="selectedImport"
label="Nextcloud Data"
:label="$t('migration.nextcloud-data')"
:rules="[rules.required]"
></v-select>
</v-col>
<v-col cols="12" md="2" sm="12">
<v-btn text color="info" @click="importRecipes"> Migrate </v-btn>
<v-btn text color="info" @click="importRecipes"> {{$t('migration.migrate')}} </v-btn>
</v-col>
<v-col cols="12" md="1" sm="12">
<v-btn text color="error" @click="deleteImportValidation">
Delete
{{$t('general.delete')}}
</v-btn>
<Confirmation
title="Delete Data"
message="Are you sure you want to delete this migration data?"
:title="$t('general.delete-data')"
:message="$t('migration.delete-confirmation')"
color="error"
icon="mdi-alert-circle"
ref="deleteThemeConfirm"
@ -39,9 +37,9 @@
</v-row>
</v-form>
<SuccessFailureAlert
success-header="Successfully Imported from Nextcloud"
:success-header="$t('migration.successfully-imported-from-nextcloud')"
:success="successfulImports"
failed-header="Failed Imports"
failed-header="$t('migration.failed-imports')"
:failed="failedImports"
/>
</v-card-text>

View file

@ -2,7 +2,7 @@
<v-form ref="file">
<v-file-input
:loading="loading"
label="Upload an Archive"
:label="$t('migration.upload-an-archive')"
v-model="file"
accept=".zip"
@change="upload"

View file

@ -1,6 +1,6 @@
<template>
<v-card :loading="loading">
<v-card-title class="headline"> Recipe Migration </v-card-title>
<v-card-title class="headline"> {{$t('migration.recipe-migration')}} </v-card-title>
<v-divider></v-divider>
<v-tabs v-model="tab">

View file

@ -5,7 +5,7 @@
</v-btn>
<v-dialog v-model="dialog" width="400">
<v-card>
<v-card-title> {{ buttonText }} Color </v-card-title>
<v-card-title> {{ buttonText }} {{$t('settings.color')}} </v-card-title>
<v-card-text>
<v-text-field v-model="color"> </v-text-field>
<v-row>
@ -26,8 +26,8 @@
</v-row>
</v-card-text>
<v-card-actions>
<v-btn text @click="toggleSwatches"> Swatches </v-btn>
<v-btn text @click="dialog = false"> Select </v-btn>
<v-btn text @click="toggleSwatches"> {{$t('settings.swatches')}} </v-btn>
<v-btn text @click="dialog = false"> {{$t('general.select')}} </v-btn>
</v-card-actions>
</v-card>
</v-dialog>

View file

@ -1,9 +1,9 @@
<template>
<div>
<v-btn text color="info" @click="dialog = true"> New </v-btn>
<v-btn text color="info" @click="dialog = true"> {{$t('general.new')}} </v-btn>
<v-dialog v-model="dialog" width="400">
<v-card>
<v-card-title> Add a New Theme </v-card-title>
<v-card-title> {{$t('settings.add-a-new-theme')}} </v-card-title>
<v-card-text>
<v-text-field
label="Theme Name"
@ -13,9 +13,9 @@
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="dialog = false"> Cancel </v-btn>
<v-btn color="grey" text @click="dialog = false"> {{$t('general.cancel')}} </v-btn>
<v-btn color="success" text @click="Select" :disabled="!themeName">
Create
{{$t('general.create')}}
</v-btn>
</v-card-actions>
</v-card>

View file

@ -1,12 +1,17 @@
<template>
<v-card>
<v-card-title class="headline"> Theme Settings </v-card-title>
<v-card-title class="headline">
{{ $t("settings.theme.theme-settings") }}
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<h2 class="mt-4 mb-1">Dark Mode</h2>
<h2 class="mt-4 mb-1">{{ $t("settings.theme.dark-mode") }}</h2>
<p>
Choose how Mealie looks to you. Set your theme preference to follow your
system settings, or choose to use the light or dark theme.
{{
$t(
"settings.theme.choose-how-mealie-looks-to-you-set-your-theme-preference-to-follow-your-system-settings-or-choose-to-use-the-light-or-dark-theme"
)
}}
</p>
<v-row dense align="center">
<v-col cols="12">
@ -18,33 +23,35 @@
>
<v-btn value="system"> Default to system </v-btn>
<v-btn value="light"> Light </v-btn>
<v-btn value="light"> {{ $t("settings.theme.light") }} </v-btn>
<v-btn value="dark"> Dark </v-btn>
<v-btn value="dark"> {{ $t("settings.theme.dark") }} </v-btn>
</v-btn-toggle>
</v-col>
</v-row></v-card-text
>
<v-divider></v-divider>
<v-card-text>
<h2 class="mt-1 mb-1">Theme</h2>
<h2 class="mt-1 mb-1">{{ $t("settings.theme.theme") }}</h2>
<p>
Select a theme from the dropdown or create a new theme. Note that the
default theme will be served to all users who have not set a theme
preference.
{{
$t(
"settings.theme.select-a-theme-from-the-dropdown-or-create-a-new-theme-note-that-the-default-theme-will-be-served-to-all-users-who-have-not-set-a-theme-preference"
)
}}
</p>
<v-form ref="form" lazy-validation>
<v-row dense align="center">
<v-col cols="12" md="4" sm="3">
<v-select
label="Saved Color Theme"
:label="$t('settings.theme.saved-color-theme')"
:items="availableThemes"
item-text="name"
return-object
v-model="selectedTheme"
@change="themeSelected"
:rules="[(v) => !!v || 'Theme is required']"
:rules="[(v) => !!v || $t('settings.theme.theme-is-required')]"
required
>
</v-select>
@ -57,8 +64,10 @@
Delete
</v-btn>
<Confirmation
title="Delete Theme"
message="Are you sure you want to delete this theme?"
:title="$t('settings.theme.delete-theme')"
:message="
$t('settings.theme.are-you-sure-you-want-to-delete-this-theme')
"
color="error"
icon="mdi-alert-circle"
ref="deleteThemeConfirm"
@ -70,43 +79,43 @@
<v-row dense align-content="center" v-if="selectedTheme.colors">
<v-col>
<ColorPickerDialog
button-text="Primary"
:button-text="$t('settings.theme.primary')"
v-model="selectedTheme.colors.primary"
/>
</v-col>
<v-col>
<ColorPickerDialog
button-text="Secondary"
:button-text="$t('settings.theme.secondary')"
v-model="selectedTheme.colors.secondary"
/>
</v-col>
<v-col>
<ColorPickerDialog
button-text="Accent"
:button-text="$t('settings.theme.accent')"
v-model="selectedTheme.colors.accent"
/>
</v-col>
<v-col>
<ColorPickerDialog
button-text="Success"
:button-text="$t('settings.theme.success')"
v-model="selectedTheme.colors.success"
/>
</v-col>
<v-col>
<ColorPickerDialog
button-text="Info"
:button-text="$t('settings.theme.info')"
v-model="selectedTheme.colors.info"
/>
</v-col>
<v-col>
<ColorPickerDialog
button-text="Warning"
:button-text="$t('settings.theme.warning')"
v-model="selectedTheme.colors.warning"
/>
</v-col>
<v-col>
<ColorPickerDialog
button-text="Error"
:button-text="$t('settings.theme.error')"
v-model="selectedTheme.colors.error"
/>
</v-col>
@ -119,7 +128,7 @@
<v-col></v-col>
<v-col align="end">
<v-btn text color="success" @click="saveThemes">
Save Colors and Apply Theme
{{ $t("settings.theme.save-colors-and-apply-theme") }}
</v-btn>
</v-col>
</v-row>

View file

@ -9,7 +9,7 @@
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-model="time"
label="Set New Time"
:label="$t('settings.set-new-time')"
prepend-icon="mdi-clock-time-four-outline"
readonly
v-bind="attrs"
@ -18,8 +18,8 @@
</template>
<v-time-picker v-if="modal2" v-model="time" full-width>
<v-spacer></v-spacer>
<v-btn text color="primary" @click="modal2 = false"> Cancel </v-btn>
<v-btn text color="primary" @click="saveTime"> OK </v-btn>
<v-btn text color="primary" @click="modal2 = false"> {{$t('general.cancel')}} </v-btn>
<v-btn text color="primary" @click="saveTime"> {{$t('general.ok')}} </v-btn>
</v-time-picker>
</v-dialog>
</template>

View file

@ -1,21 +1,17 @@
<template>
<v-card>
<v-card-title class="headline">
Meal Planner Webhooks
{{$t('settings.webhooks.meal-planner-webhooks')}}
</v-card-title>
<v-card-text>
<p>
The URLs listed below will recieve webhooks containing the recipe data
for the meal plan on it's scheduled day. Currently Webhooks will execute
at <strong>{{ time }}</strong>
</p>
<p v-html="$t('settings.webhooks.the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at', {time: time})"></p>
<v-row dense align="center">
<v-col cols="12" md="2" sm="5">
<v-switch
v-model="enabled"
inset
label="Enabled"
:label="$t('general.enabled')"
class="my-n3"
></v-switch>
</v-col>
@ -23,7 +19,7 @@
<TimePickerDialog @save-time="saveTime" />
</v-col>
<v-col cols="12" md="4" sm="5">
<v-btn text color="info" @click="testWebhooks"> Test Webhooks </v-btn>
<v-btn text color="info" @click="testWebhooks"> {{$t('settings.webhooks.test-webhooks')}} </v-btn>
</v-col>
</v-row>
@ -36,7 +32,7 @@
<v-col>
<v-text-field
v-model="webhooks[index]"
label="Webhook URL"
:label="$t('settings.webhooks.webhook-url')"
></v-text-field>
</v-col>
</v-row>
@ -51,7 +47,7 @@
<v-col> </v-col>
<v-col align="end">
<v-btn text color="success" @click="saveWebhooks">
Save Webhooks
{{$t('settings.webhooks.save-webhooks')}}
</v-btn>
</v-col>
</v-row>

View file

@ -2,16 +2,20 @@
<div class="text-center">
<v-dialog v-model="addRecipe" width="650" @click:outside="reset">
<v-card :loading="processing">
<v-card-title class="headline"> From URL </v-card-title>
<v-card-title class="headline"
>{{ $t("new-recipe.from-url") }}
</v-card-title>
<v-card-text>
<v-form>
<v-text-field v-model="recipeURL" label="Recipe URL"></v-text-field>
<v-text-field
v-model="recipeURL"
:label="$t('new-recipe.recipe-url')"
></v-text-field>
</v-form>
<v-alert v-if="error" color="red" outlined type="success">
Looks like there was an error parsing the URL. Check the log and
debug/last_recipe.json to see what went wrong.
{{ $t("new-recipe.error-message") }}
</v-alert>
</v-card-text>
@ -19,8 +23,12 @@
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="reset"> Close </v-btn>
<v-btn color="success" text @click="createRecipe"> Submit </v-btn>
<v-btn color="grey" text @click="reset">
{{ $t("general.close") }}
</v-btn>
<v-btn color="success" text @click="createRecipe">
{{ $t("general.submit") }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
@ -66,6 +74,7 @@ export default {
this.addRecipe = false;
this.processing = false;
this.recipeURL = "";
this.$router.push(`/recipe/${response.data}`);
},

View file

@ -13,20 +13,20 @@
v-model="user.name"
light="light"
prepend-icon="person"
label="Name"
:label="$t('general.name')"
></v-text-field>
<v-text-field
v-model="user.email"
light="light"
prepend-icon="mdi-email"
label="Email"
:label="$t('login.email')"
type="email"
></v-text-field>
<v-text-field
v-model="user.password"
light="light"
prepend-icon="mdi-lock"
label="Password"
:label="$t('login.password')"
type="password"
></v-text-field>
<v-checkbox
@ -34,7 +34,7 @@
v-if="options.isLoggingIn"
v-model="options.shouldStayLoggedIn"
light="light"
label="Stay logged in?"
:label="$t('login.stay-logged-in')"
hide-details="hide-details"
></v-checkbox>
<v-btn
@ -44,14 +44,14 @@
color="primary"
block="block"
type="submit"
>Sign in</v-btn
>{{$t('login.sign-in')}}</v-btn
>
<v-btn
v-else
block="block"
type="submit"
@click.prevent="options.isLoggingIn = true"
>Sign up</v-btn
>{{$t('login.sign-up')}}</v-btn
>
</v-form>
</v-card-text>

View file

@ -32,26 +32,32 @@
<script>
export default {
data: () => ({
items: [
{
icon: "mdi-calendar-week",
title: "Dinner This Week",
nav: "/meal-plan/this-week",
},
{
icon: "mdi-calendar-today",
title: "Dinner Today",
nav: "/meal-plan/today",
},
{
icon: "mdi-calendar-multiselect",
title: "Planner",
nav: "/meal-plan/planner",
},
{ icon: "mdi-cog", title: "Settings", nav: "/settings/site" },
],
}),
data: function () {
return {
items: [
{
icon: "mdi-calendar-week",
title: this.$i18n.t("meal-plan.dinner-this-week"),
nav: "/meal-plan/this-week",
},
{
icon: "mdi-calendar-today",
title: this.$i18n.t("meal-plan.dinner-today"),
nav: "/meal-plan/today",
},
{
icon: "mdi-calendar-multiselect",
title: this.$i18n.t("meal-plan.planner"),
nav: "/meal-plan/planner",
},
{
icon: "mdi-cog",
title: this.$i18n.t("general.settings"),
nav: "/settings/site",
},
],
};
},
methods: {
navRouter(route) {
this.$router.push(route);

View file

@ -26,7 +26,7 @@
<v-tooltip top color="secondary" max-width="400" open-delay="50">
<template v-slot:activator="{ on, attrs }">
<v-btn color="secondary" v-on="on" v-bind="attrs" text
>Description</v-btn
>{{$t('recipe.description')}}</v-btn
>
</template>
<span>{{ description }}</span>

View file

@ -9,15 +9,13 @@
hide-details
hide-selected
item-text="slug"
label="Search for a Recipe"
:label="$t('search.search-for-a-recipe')"
single-line
@keyup.enter.native="moreInfo(selected)"
>
<template v-slot:no-data>
<v-list-item>
<v-list-item-title>
Search for your Favorite
<strong>Recipe</strong>
<v-list-item-title :v-html="$t('search.search-for-your-favorite-recipe')">
</v-list-item-title>
</v-list-item>
</template>

View file

@ -0,0 +1,108 @@
<template>
<div>
<v-autocomplete
:items="autoResults"
item-value="item.slug"
item-text="item.name"
dense
light
label="Search Mealie"
:search-input.sync="search"
hide-no-data
cache-items
solo
>
<template
v-if="showResults"
v-slot:item="{ item }"
style="max-width: 750px"
>
<v-list-item-avatar>
<v-img :src="getImage(item.item.image)"></v-img>
</v-list-item-avatar>
<v-list-item-content @click="selected(item.item.slug)">
<v-list-item-title>
{{ item.item.name }}
<v-rating
dense
v-if="item.item.rating"
:value="item.item.rating"
size="12"
>
</v-rating>
</v-list-item-title>
<v-list-item-subtitle>
{{ item.item.description }}
</v-list-item-subtitle>
</v-list-item-content>
</template>
</v-autocomplete>
</div>
</template>
<script>
import Fuse from "fuse.js";
import utils from "../../utils";
export default {
props: {
showResults: {
default: false,
},
},
data() {
return {
search: "",
result: [],
autoResults: [],
isDark: false,
options: {
shouldSort: true,
threshold: 0.6,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: ["name", "slug"],
},
};
},
mounted() {
this.isDark = this.$store.getters.getIsDark;
},
computed: {
data() {
return this.$store.getters.getRecentRecipes;
},
fuse() {
return new Fuse(this.data, this.options);
},
},
watch: {
search() {
if (this.search.trim() === "") this.result = this.list;
else this.result = this.fuse.search(this.search.trim());
console.log("test");
this.$emit("results", this.result);
if (this.showResults === true) {
this.autoResults = this.result;
}
},
},
methods: {
getImage(image) {
return utils.getImageURL(image);
},
selected(slug) {
this.$emit("selected", slug);
},
},
};
</script>
<style>
.color-transition {
transition: background-color 0.3s ease;
}
</style>

View file

@ -5,7 +5,7 @@
<template v-slot:action="{ attrs }">
<v-btn color="white" text v-bind="attrs" @click="close(false)">
Close
{{$t('general.close')}}
</v-btn>
</template>
</v-snackbar>