* fix links

* actually fix #238

* Feature/mkdocs version bump (#240)

* fix links (#239)

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

* fix #238

* bump mkdocs version

* light/dark toggle

* light/dark mode css

* API_DOCS defaults to True

* disable build on push for master

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

* Feature/recipe viewer (#244)

* fix dialog placement

* markdown support in ingredients

* fix line render issue

* fix tag rendering bug

* change ingredients to text area

* no slug error

* add tag pages

* remove console.logs

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

* changelog v0.4.1

* bug/backup-download (#245)

* fix blocked download

* + download blocked

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

* Feature/meal planner (#246)

* fixes duplicate recipes in meal-plan #221

* add quick week option

* scope css

* add mealplanner info

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

* Nextcloud Import Bugs - #248 (#250)

* parses datetime properly + clean category - #248

* add default credentials to docs

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

* Add bulk import examples to docs. (#252)

* Add bulk import examples to docs.

* Update api-usage.md

* Add Python example for bulk import.

* Change IP address in API example.

* Refactor/app settings (#251)

* fix env setup bugs

* remove unused import

* fix layout issues

* changelog

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

* env setup fixes

* Feature/about api (#253)

* fix settings

* app info cleanup

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

* Feature/image minify (#256)

* fix settings

* app info cleanup

* bottom-bar experiment

* remove dup key

* type hints

* add dependency

* updated image with query parameters

* read image options

* add image minification

* add image minification step

* alt image routes

* add image minification

* set mobile bar to top

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

* Feature/additional endpoints (#257)

* new recipe summary route

* add categories to cards

* add pillow

* show tags instead of categories

* additional debug info

* add todays meal image url

* about page

* fix reactive tag

* changelog + docs

* bump version

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

* add pillow dependencies (#258)

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

* Feature/search page (#259)

* add pillow dependencies

* advanced search page

* advanced search apge

* remove extra dependencies

* add pre-run script

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

* no image assignment

* advanced search

* fix docker dev build

* Do not force theme settings on login form (#260)

* Fix docker dev db persistence (#264)

* Fix docker dev db persistence

* Make run.sh the only startup script for prod + dev

Credits to @hay-kot for run.sh script logic

* Restore dev backend initialization in non-docker setup

* Make run.sh POSIX-friendly

* Allow dev backend to auto-reload in Docker

* Frontend Refactor + Bug Fixes

* merge category and tag selector

* unifiy category selector

* add hint

* spacing

* fix nextcloud migration

* simplify email validator #261

* formatting

* cleanup

* auto-gen

* format

* update run script

* unified category/tag selector

* rename component

* Add advanced search link

* remove old code

* convert keywords to tags

* add proper behavior on rename

* proper image name association on rename

* fix test cleanup

* changelog

* set docker comppand

* minify on migration

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

* bug-fixes/category-tag-creator (#266)

* fix category labels

* set loader for migration

* v0.4.1

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

* Hot Fix (#269)

* fix category labels

* set loader for migration

* v0.4.1

* reorganize API docs

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

* Fix some pytests (#265)

* Fix encoding issue in cleaner unit test

* Add VS Code task to run pytests

* Fix FileExistsError when running Windows

* Add Portuguese Translation (#232)

* Add Portuguese Translation

* add portuguese translation option

* formatting

* add missing }

* specify absolute path

* Feature/migration-rewrite (#278)

* start

* migration rewrite

* update name

* convert chowdown to new methods

* refactor/remove duplicate code

* refactor to unify logger + log to file

* remove toolbox

* Display report on UI

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

* refactor/image-minification (#285)

* refactor image minification calls

* remove nusiance logs

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

* feature/debug-info (#286)

* rename 'ENV' to 'PRODUCTION' and default to true

* set env PRODUCTION

* refactor file download process

* add last_recipe.json and log downloads

* changelog + version bump

* set env on workflows

* bump version

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

* Basic nutrition editor (#288)

* Basic nutrition editor

* fix no image on scrape

* nutrition display

* add recipe images

* update by url

* new upload options

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

* Sanitize recipe backup filenames (#287)

Fixes #275

* fix page creation fixes #290

* Display categories in sidebar if no pages set (#292)

Fixes  #291

* Enrich page title with context (#296)

- Static pages have their own titles
- The name of the recipe is displayed when viewing it

* fix: translates phrase for locale de (#298)

Co-authored-by: Jonas  Schubert <jonas.schubert.1990@web.de>

* Fix ingredient checkbox click (#305)

Fixes #304
v-list-item already flips the checkbox

* Localize custom pages and search page (#299)

* Localize custom pages and search page

* Fix FR translation for step

* fixes #306

* fixes #297

* Update changelog

* generate docs

Co-authored-by: hay-kot <hay-kot@pm.me>
Co-authored-by: Nat <nathanynath@yahoo.fr>
Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com>
Co-authored-by: Pedro Mata Rodrigues <pmmatarodrigues@gmail.com>
Co-authored-by: JonasSchubert <jonas.schubert.projects@web.de>
Co-authored-by: Jonas  Schubert <jonas.schubert.1990@web.de>
This commit is contained in:
Hayden
2021-04-17 12:57:47 -08:00
committed by GitHub
parent e11577f786
commit 0f5a564ff3
68 changed files with 3357 additions and 2521 deletions

View File

@@ -37,14 +37,7 @@
<v-divider></v-divider>
<v-card-actions>
<v-btn
color="accent"
text
:loading="downloading"
@click="downloadFile(`/api/backups/${name}/download`)"
>
{{ $t("general.download") }}
</v-btn>
<TheDownloadBtn :download-url="downloadUrl" />
<v-spacer></v-spacer>
<v-btn color="error" text @click="raiseEvent('delete')">
{{ $t("general.delete") }}
@@ -66,9 +59,10 @@
<script>
import ImportOptions from "@/components/Admin/Backup/ImportOptions";
import axios from "axios";
import TheDownloadBtn from "@/components/UI/TheDownloadBtn.vue";
import { backupURLs } from "@/api/backup";
export default {
components: { ImportOptions },
components: { ImportOptions, TheDownloadBtn },
props: {
name: {
default: "Backup Name",
@@ -92,6 +86,11 @@ export default {
downloading: false,
};
},
computed: {
downloadUrl() {
return backupURLs.downloadBackup(this.name);
},
},
methods: {
updateOptions(options) {
this.options = options;
@@ -116,23 +115,6 @@ export default {
this.close();
this.$emit(event, eventData);
},
async downloadFile(downloadURL) {
this.downloading = true;
const response = await axios({
url: downloadURL,
method: "GET",
responseType: "blob", // important
});
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", `${this.name}.zip`);
document.body.appendChild(link);
link.click();
this.downloading = false;
},
},
};
</script>

View File

@@ -45,7 +45,7 @@
</template>
<script>
import DataTable from "./DataTable";
import DataTable from "@/components/Admin/Backup/ImportSummaryDialog/DataTable";
export default {
components: {
DataTable,
@@ -145,4 +145,4 @@ export default {
</script>
<style>
</style>
</style>

View File

@@ -17,13 +17,13 @@
<v-text-field
autofocus
v-model="page.name"
label="Page Name"
:label="$t('settings.page-name')"
></v-text-field>
<CategoryTagSelector
v-model="page.categories"
ref="categoryFormSelector"
@mounted="catMounted = true"
tag-selector="false"
:tag-selector="false"
/>
</v-card-text>

View File

@@ -3,10 +3,10 @@
<CreatePageDialog ref="createDialog" @refresh-page="getPages" />
<v-card-text>
<h2 class="mt-1 mb-1 ">
Custom Pages
{{$t('settings.custom-pages')}}
<span>
<v-btn color="success" @click="newPage" small class="ml-3">
Create
{{$t('general.create')}}
</v-btn>
</span>
</h2>
@@ -41,11 +41,11 @@
<v-card-actions>
<v-btn text small color="error" @click="deletePage(item.id)">
Delete
{{$t('general.delete')}}
</v-btn>
<v-spacer> </v-spacer>
<v-btn small text color="success" @click="editPage(index)">
Edit
{{$t('general.edit')}}
</v-btn>
</v-card-actions>
</v-card>
@@ -55,7 +55,7 @@
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="success" @click="savePages">
Save
{{$t('general.save')}}
</v-btn>
</v-card-actions>
</v-card>
@@ -76,8 +76,8 @@ export default {
customPages: [],
newPageData: {
create: true,
title: "New Page",
buttonText: "Create",
title: this.$t('settings.new-page'),
buttonText: this.$t('general.create'),
data: {
name: "",
categories: [],
@@ -86,8 +86,8 @@ export default {
},
editPageData: {
create: false,
title: "Edit Page",
buttonText: "Update",
title: this.$t('settings.edit-page'),
buttonText: this.$t('general.update'),
data: {},
},
};

View File

@@ -1,5 +1,6 @@
<template>
<v-card outlined class="my-2" :loading="loading">
<MigrationDialog ref="migrationDialog" />
<v-card-title>
{{ title }}
<v-spacer></v-spacer>
@@ -67,6 +68,7 @@
import UploadBtn from "../../UI/UploadBtn";
import utils from "@/utils";
import { api } from "@/api";
import MigrationDialog from "@/components/Admin/Migration/MigrationDialog.vue";
export default {
props: {
folder: String,
@@ -76,6 +78,7 @@ export default {
},
components: {
UploadBtn,
MigrationDialog,
},
data() {
return {
@@ -90,7 +93,8 @@ export default {
async importMigration(file_name) {
this.loading = true;
let response = await api.migrations.import(this.folder, file_name);
this.$emit("imported", response.successful, response.failed);
this.$refs.migrationDialog.open(response);
// this.$emit("imported", response.successful, response.failed);
this.loading = false;
},
readableTime(timestamp) {

View File

@@ -0,0 +1,109 @@
<template>
<div class="text-center">
<v-dialog v-model="dialog" width="70%">
<v-card>
<v-app-bar dark color="primary mb-2">
<v-icon large left>
mdi-import
</v-icon>
<v-toolbar-title class="headline">
Migration Summary
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-card-text class="mb-n4">
<v-row>
<div v-for="values in allNumbers" :key="values.title">
<v-card-text>
<div>
<h3>{{ values.title }}</h3>
</div>
<div class="success--text">Success: {{ values.success }}</div>
<div class="error--text">Failed: {{ values.failure }}</div>
</v-card-text>
</div>
</v-row>
</v-card-text>
<v-tabs v-model="tab">
<v-tab>{{ $t("general.recipes") }}</v-tab>
</v-tabs>
<v-tabs-items v-model="tab">
<v-tab-item v-for="(table, index) in allTables" :key="index">
<v-card flat>
<DataTable :data-headers="importHeaders" :data-set="table" />
</v-card>
</v-tab-item>
</v-tabs-items>
</v-card>
</v-dialog>
</div>
</template>
<script>
import DataTable from "@/components/Admin/Backup/ImportSummaryDialog/DataTable";
export default {
components: {
DataTable,
},
data: () => ({
tab: null,
dialog: false,
recipeData: [],
themeData: [],
settingsData: [],
userData: [],
groupData: [],
pageData: [],
importHeaders: [
{
text: "Status",
value: "status",
},
{
text: "Name",
align: "start",
sortable: true,
value: "name",
},
{ text: "Exception", value: "data-table-expand", align: "center" },
],
allDataTables: [],
}),
computed: {
recipeNumbers() {
return this.calculateNumbers(this.$t("general.recipes"), this.recipeData);
},
allNumbers() {
return [this.recipeNumbers];
},
allTables() {
return [this.recipeData];
},
},
methods: {
calculateNumbers(title, list_array) {
if (!list_array) return;
let numbers = { title: title, success: 0, failure: 0 };
list_array.forEach(element => {
if (element.status) {
numbers.success++;
} else numbers.failure++;
});
return numbers;
},
open(importData) {
this.recipeData = importData;
this.dialog = true;
},
},
};
</script>
<style>
</style>

View File

@@ -90,7 +90,7 @@ export default {
computed: {
inputLabel() {
if (!this.showLabel) return null;
return this.tagSelector ? "Tags" : "Categories";
return this.tagSelector ? this.$t('recipe.tags') : this.$t('recipe.categories');
},
activeItems() {
let ItemObjects = [];

View File

@@ -0,0 +1,76 @@
<template>
<div class="text-center">
<v-menu offset-y top nudge-top="6" :close-on-content-click="false">
<template v-slot:activator="{ on, attrs }">
<v-btn color="accent" dark v-bind="attrs" v-on="on">
Image
</v-btn>
</template>
<v-card width="400">
<v-card-title class="headline flex mb-0">
<div>
Recipe Image
</div>
<UploadBtn
class="ml-auto"
url="none"
file-name="image"
:text-btn="false"
@uploaded="uploadImage"
/>
</v-card-title>
<v-card-text class="mt-n5">
<div>
<v-text-field label="URL" class="pt-5" clearable v-model="url">
<template v-slot:append-outer>
<v-btn
class="ml-2"
color="primary"
@click="getImageFromURL"
:loading="loading"
>
Get
</v-btn>
</template>
</v-text-field>
</div>
</v-card-text>
</v-card>
</v-menu>
</div>
</template>
<script>
const REFRESH_EVENT = "refresh";
const UPLOAD_EVENT = "upload";
import UploadBtn from "@/components/UI/UploadBtn";
import { api } from "@/api";
// import axios from "axios";
export default {
components: {
UploadBtn,
},
props: {
slug: String,
},
data: () => ({
items: [{ title: "Upload Image" }, { title: "From URL" }],
url: "",
loading: false,
}),
methods: {
uploadImage(fileObject) {
this.$emit(UPLOAD_EVENT, fileObject);
},
async getImageFromURL() {
this.loading = true;
const response = await api.recipes.updateImagebyURL(this.slug, this.url);
if (response) this.$emit(REFRESH_EVENT);
this.loading = false;
},
},
};
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,81 @@
<template>
<div v-if="valueNotNull || edit">
<h2 class="my-4">Nutrition</h2>
<div v-if="edit">
<div v-for="(item, key, index) in value" :key="index">
<v-text-field
dense
:value="value[key]"
:label="labels[key].label"
:suffix="labels[key].suffix"
type="number"
autocomplete="off"
@input="updateValue(key, $event)"
></v-text-field>
</div>
</div>
<div v-if="showViewer">
<v-list dense>
<v-list-item-group color="primary">
<v-list-item v-for="(item, key, index) in labels" :key="index">
<v-list-item-content>
<v-list-item-title class="pl-4 text-subtitle-1 flex row ">
<div>{{ item.label }}</div>
<div class="ml-auto mr-1">{{ value[key] }}</div>
<div>{{ item.suffix }}</div>
</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</div>
</div>
</template>
<script>
export default {
props: {
value: {},
edit: {
type: Boolean,
default: true,
},
},
data() {
return {
labels: {
calories: {
label: "Calories",
suffix: "calories",
},
fatContent: { label: "Fat Content", suffix: "grams" },
fiberContent: { label: "Fiber Content", suffix: "grams" },
proteinContent: { label: "Protein Content", suffix: "grams" },
sodiumContent: { label: "Sodium Content", suffix: "milligrams" },
sugarContent: { label: "Sugar Content", suffix: "grams" },
},
};
},
computed: {
showViewer() {
return !this.edit && this.valueNotNull;
},
valueNotNull() {
for (const property in this.value) {
const valueProperty = this.value[property];
if (valueProperty && valueProperty !== "") return true;
}
return false;
},
},
methods: {
updateValue(key, value) {
this.$emit("input", { ...this.value, [key]: value });
},
},
};
</script>
<style lang="scss" scoped>
</style>

View File

@@ -2,16 +2,12 @@
<v-form ref="form">
<v-card-text>
<v-row dense>
<v-col cols="3"></v-col>
<v-col>
<v-file-input
v-model="fileObject"
:label="$t('general.image-file')"
truncate-length="30"
@change="uploadImage"
></v-file-input>
</v-col>
<v-col cols="3"></v-col>
<ImageUploadBtn
class="mt-2"
@upload="uploadImage"
:slug="value.slug"
@refresh="$emit('upload')"
/>
</v-row>
<v-row dense>
<v-col>
@@ -92,7 +88,7 @@
auto-grow
solo
dense
rows="2"
rows="1"
>
<v-icon
class="mr-n1"
@@ -165,6 +161,7 @@
<v-btn class="mt-1" color="secondary" fab dark small @click="addNote">
<v-icon>mdi-plus</v-icon>
</v-btn>
<NutritionEditor v-model="value.nutrition" :edit="true" />
<ExtrasEditor :extras="value.extras" @save="saveExtras" />
</v-col>
@@ -222,17 +219,20 @@
<script>
import draggable from "vuedraggable";
import { api } from "@/api";
import utils from "@/utils";
import BulkAdd from "./BulkAdd";
import ExtrasEditor from "./ExtrasEditor";
import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector";
import NutritionEditor from "./NutritionEditor";
import ImageUploadBtn from "./ImageUploadBtn.vue";
export default {
components: {
BulkAdd,
ExtrasEditor,
draggable,
CategoryTagSelector,
NutritionEditor,
ImageUploadBtn,
},
props: {
value: Object,
@@ -251,12 +251,8 @@ export default {
};
},
methods: {
uploadImage() {
this.$emit("upload", this.fileObject);
},
async updateImage() {
const slug = this.value.slug;
api.recipes.updateImage(slug, this.fileObject);
uploadImage(fileObject) {
this.$emit("upload", fileObject);
},
toggleDisabled(stepIndex) {
if (this.disabledSteps.includes(stepIndex)) {

View File

@@ -12,6 +12,7 @@
v-model="ingredient.checked"
class="pt-0 my-auto py-auto"
color="secondary"
:readonly="true"
>
</v-checkbox>

View File

@@ -40,6 +40,7 @@
:isCategory="false"
/>
<Notes :notes="notes" />
<NutritionEditor :value="nutrition" :edit="false" />
</div>
</v-col>
<v-divider
@@ -56,6 +57,7 @@
<RecipeChips :title="$t('recipe.categories')" :items="categories" />
<RecipeChips :title="$t('recipe.tags')" :items="tags" />
<Notes :notes="notes" />
<NutritionEditor :value="nutrition" :edit="false" />
</div>
<v-row class="mt-2 mb-1">
<v-col></v-col>
@@ -80,6 +82,7 @@
</template>
<script>
import NutritionEditor from "@/components/Recipe/RecipeEditor/NutritionEditor";
import VueMarkdown from "@adapttive/vue-markdown";
import utils from "@/utils";
import RecipeChips from "./RecipeChips";
@@ -93,6 +96,7 @@ export default {
Steps,
Notes,
Ingredients,
NutritionEditor,
},
props: {
name: String,
@@ -105,6 +109,7 @@ export default {
rating: Number,
yields: String,
orgURL: String,
nutrition: Object,
},
data() {
return {

View File

@@ -5,7 +5,7 @@
<v-row v-if="title != null">
<v-col>
<v-btn-toggle group>
<v-btn text :to="`/recipes/${title.toLowerCase()}`">
<v-btn text>
{{ title.toUpperCase() }}
</v-btn>
</v-btn-toggle>

View File

@@ -54,22 +54,11 @@ export default {
{
icon: "mdi-magnify",
to: "/search",
title: "search",
title: this.$t('search.search'),
},
],
};
},
computed: {
allCategories() {
return this.$store.getters.getCategories;
},
},
watch: {
allCategories() {
this.buildSidebar();
},
showSidebar() {},
},
mounted() {
this.buildSidebar();
this.mobile = this.viewScale();
@@ -81,14 +70,27 @@ export default {
this.links = [];
this.links.push(...this.baseLinks);
const pages = await api.siteSettings.getPages();
pages.sort((a, b) => a.position - b.position);
pages.forEach(async element => {
this.links.push({
title: element.name,
to: `/pages/${element.slug}`,
icon: "mdi-tag",
if(pages.length > 0) {
pages.sort((a, b) => a.position - b.position);
pages.forEach(async element => {
this.links.push({
title: element.name,
to: `/pages/${element.slug}`,
icon: "mdi-tag",
});
});
});
}
else {
const categories = await api.categories.getAll();
categories.forEach(async element => {
this.links.push({
title: element.name,
to: `/recipes/category/${element.slug}`,
icon: "mdi-tag",
});
});
}
},
viewScale() {
switch (this.$vuetify.breakpoint.name) {

View File

@@ -0,0 +1,51 @@
<template>
<v-btn color="accent" text :loading="downloading" @click="downloadFile">
{{ showButtonText }}
</v-btn>
</template>
<script>
/**
* The download button used for the entire site
* pass a URL to the endpoint that will return a
* file_token which will then be used to request the file
* from the server and open that link in a new tab
*/
import { apiReq } from "@/api/api-utils";
export default {
props: {
/**
* URL to get token from
*/
downloadUrl: {
default: "",
},
/**
* Override button text. Defaults to "Download"
*/
buttonText: {
default: null,
},
},
data() {
return {
downloading: false,
};
},
computed: {
showButtonText() {
return this.buttonText || this.$t("general.download");
},
},
methods: {
async downloadFile() {
this.downloading = true;
await apiReq.download(this.downloadUrl);
this.downloading = false;
},
},
};
</script>
<style lang="scss" scoped>
</style>

View File

@@ -1,7 +1,12 @@
<template>
<v-form ref="file">
<input ref="uploader" class="d-none" type="file" @change="onFileChanged" />
<v-btn :loading="isSelecting" @click="onButtonClick" color="accent" text>
<v-btn
:loading="isSelecting"
@click="onButtonClick"
color="accent"
:text="textBtn"
>
<v-icon left> {{ icon }}</v-icon>
{{ text ? text : defaultText }}
</v-btn>
@@ -13,10 +18,17 @@ const UPLOAD_EVENT = "uploaded";
import { api } from "@/api";
export default {
props: {
post: {
type: Boolean,
default: true,
},
url: String,
text: { default: "Upload" },
icon: { default: "mdi-cloud-upload" },
fileName: { default: "archive" },
textBtn: {
default: true,
},
},
data: () => ({
file: null,
@@ -33,6 +45,12 @@ export default {
async upload() {
if (this.file != null) {
this.isSelecting = true;
if (this.post) {
this.$emit(UPLOAD_EVENT, this.file);
this.isSelecting = false;
return;
}
let formData = new FormData();
formData.append(this.fileName, this.file);