feature/recipe-patch-improvements (#382)

* automated docs update

* recipe rating component

* recipe partial updates - closes #25

* use Vue.delete to update store

* format

* arrow functions

* fix tests

* format

* initial context menu

* localize

* add confirmation dialog

* context menu

* fix bare exception

* update line length

* format all file with prettier

* update changelog

* download as json

* update python dependencies

* update javascript dependencies

Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
Hayden 2021-05-01 20:46:02 -08:00 committed by GitHub
parent c196445e61
commit be378cb20c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
121 changed files with 18942 additions and 4765 deletions

View File

@ -46,12 +46,12 @@ jobs:
#---------------------------------------------- #----------------------------------------------
# load cached venv if cache exists # load cached venv if cache exists
#---------------------------------------------- #----------------------------------------------
# - name: Load cached venv - name: Load cached venv
# id: cached-poetry-dependencies id: cached-poetry-dependencies
# uses: actions/cache@v2 uses: actions/cache@v2
# with: with:
# path: .venv path: .venv
# key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
#---------------------------------------------- #----------------------------------------------
# install dependencies if cache does not exist # install dependencies if cache does not exist
#---------------------------------------------- #----------------------------------------------

View File

@ -27,6 +27,12 @@
- Title case all Categories or Tags with 1 click - Title case all Categories or Tags with 1 click
- Create/Rename/Delete Operations for Tags/Categories - Create/Rename/Delete Operations for Tags/Categories
- Remove Unused Categories or Tags with 1 click - Remove Unused Categories or Tags with 1 click
- Recipe Cards now have a menu button for quick actions!
- Edit
- Delete
- Download (As Json)
- Copy Link
- Rating can be updated without entering the editor - Closes #25
### Performance ### Performance
- Images are now served up by the Caddy increase performance and offloading some loads from the API server - Images are now served up by the Caddy increase performance and offloading some loads from the API server

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,3 @@
module.exports = { module.exports = {
presets: [ presets: ["@vue/cli-plugin-babel/preset"],
'@vue/cli-plugin-babel/preset' };
]
}

17065
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@
"@mdi/font": "^5.9.55", "@mdi/font": "^5.9.55",
"@vue/cli-plugin-babel": "^4.5.11", "@vue/cli-plugin-babel": "^4.5.11",
"@vue/cli-plugin-eslint": "^4.5.11", "@vue/cli-plugin-eslint": "^4.5.11",
"@vue/cli-service": "^4.5.11", "@vue/cli-service": "^4.1.1",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"eslint": "^6.7.2", "eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2", "eslint-plugin-vue": "^6.2.2",
@ -65,6 +65,7 @@
"trailingComma": "es5", "trailingComma": "es5",
"tabWidth": 2, "tabWidth": 2,
"semi": true, "semi": true,
"singleQuote": false "singleQuote": false,
"printWidth": 120
} }
} }

View File

@ -4,9 +4,7 @@
<TheAppBar /> <TheAppBar />
<v-main> <v-main>
<v-banner v-if="demo" sticky <v-banner v-if="demo" sticky
><div class="text-center"> ><div class="text-center"><b> This is a Demo</b> | Username: changeme@email.com | Password: demo</div></v-banner
<b> This is a Demo</b> | Username: changeme@email.com | Password: demo
</div></v-banner
> >
<router-view></router-view> <router-view></router-view>
</v-main> </v-main>
@ -57,9 +55,7 @@ export default {
*/ */
darkModeSystemCheck() { darkModeSystemCheck() {
if (this.$store.getters.getDarkMode === "system") if (this.$store.getters.getDarkMode === "system")
Vuetify.framework.theme.dark = window.matchMedia( Vuetify.framework.theme.dark = window.matchMedia("(prefers-color-scheme: dark)").matches;
"(prefers-color-scheme: dark)"
).matches;
}, },
/** /**
* This will monitor the OS level darkmode and call to update dark mode. * This will monitor the OS level darkmode and call to update dark mode.

View File

@ -3,18 +3,16 @@ import axios from "axios";
import { store } from "../store"; import { store } from "../store";
import utils from "@/utils"; import utils from "@/utils";
axios.defaults.headers.common[ axios.defaults.headers.common["Authorization"] = `Bearer ${store.getters.getToken}`;
"Authorization"
] = `Bearer ${store.getters.getToken}`;
function handleError(error, getText) { function handleError(error, getText) {
if(getText) { if (getText) {
utils.notify.error(getText(error.response)); utils.notify.error(getText(error.response));
} }
return false; return false;
} }
function handleResponse(response, getText) { function handleResponse(response, getText) {
if(response && getText) { if (response && getText) {
const successText = getText(response); const successText = getText(response);
utils.notify.success(successText); utils.notify.success(successText);
} }
@ -31,26 +29,36 @@ function defaultSuccessText(response) {
const apiReq = { const apiReq = {
post: async function(url, data, getErrorText = defaultErrorText, getSuccessText) { post: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
const response = await axios.post(url, data).catch(function(error) { handleError(error, getErrorText) }); const response = await axios.post(url, data).catch(function(error) {
return handleResponse(response, getSuccessText); handleError(error, getErrorText);
}, });
put: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
const response = await axios.put(url, data).catch(function(error) { handleError(error, getErrorText) });
return handleResponse(response, getSuccessText); return handleResponse(response, getSuccessText);
}, },
put: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
const response = await axios.put(url, data).catch(function(error) {
handleError(error, getErrorText);
});
return handleResponse(response, getSuccessText);
},
patch: async function(url, data, getErrorText = defaultErrorText, getSuccessText) { patch: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
const response = await axios.patch(url, data).catch(function(error) { handleError(error, getErrorText) }); const response = await axios.patch(url, data).catch(function(error) {
handleError(error, getErrorText);
});
return handleResponse(response, getSuccessText); return handleResponse(response, getSuccessText);
}, },
get: function(url, data, getErrorText = defaultErrorText) { get: function(url, data, getErrorText = defaultErrorText) {
return axios.get(url, data).catch(function(error) { handleError(error, getErrorText) }); return axios.get(url, data).catch(function(error) {
handleError(error, getErrorText);
});
}, },
delete: async function(url, data, getErrorText = defaultErrorText, getSuccessText = defaultSuccessText ) { delete: async function(url, data, getErrorText = defaultErrorText, getSuccessText = defaultSuccessText) {
const response = await axios.delete(url, data).catch( function(error) { handleError(error, getErrorText) } ); const response = await axios.delete(url, data).catch(function(error) {
handleError(error, getErrorText);
});
return handleResponse(response, getSuccessText); return handleResponse(response, getSuccessText);
}, },

View File

@ -1,7 +1,7 @@
import { baseURL } from "./api-utils"; import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils"; import { apiReq } from "./api-utils";
import { store } from "@/store"; import { store } from "@/store";
import i18n from '@/i18n.js'; import i18n from "@/i18n.js";
const backupBase = baseURL + "backups/"; const backupBase = baseURL + "backups/";
@ -14,8 +14,6 @@ export const backupURLs = {
downloadBackup: fileName => `${backupBase}${fileName}/download`, downloadBackup: fileName => `${backupBase}${fileName}/download`,
}; };
export const backupAPI = { export const backupAPI = {
/** /**
* Request all backups available on the server * Request all backups available on the server
@ -44,8 +42,8 @@ export const backupAPI = {
return apiReq.delete( return apiReq.delete(
backupURLs.deleteBackup(fileName), backupURLs.deleteBackup(fileName),
null, null,
function() { return i18n.t('settings.backup.unable-to-delete-backup'); }, () => i18n.t("settings.backup.unable-to-delete-backup"),
function() { return i18n.t('settings.backup.backup-deleted'); } () => i18n.t("settings.backup.backup-deleted")
); );
}, },
/** /**
@ -55,10 +53,12 @@ export const backupAPI = {
*/ */
async create(options) { async create(options) {
return apiReq.post( return apiReq.post(
backupURLs.createBackup, backupURLs.createBackup,
options, options,
function() { return i18n.t('settings.backup.error-creating-backup-see-log-file'); }, () => i18n.t("settings.backup.error-creating-backup-see-log-file"),
function(response) { return i18n.t('settings.backup.backup-created-at-response-export_path', {path: response.data.export_path}); } response => {
return i18n.t("settings.backup.backup-created-at-response-export_path", { path: response.data.export_path });
}
); );
}, },
/** /**

View File

@ -1,7 +1,7 @@
import { baseURL } from "./api-utils"; import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils"; import { apiReq } from "./api-utils";
import { store } from "@/store"; import { store } from "@/store";
import i18n from '@/i18n.js'; import i18n from "@/i18n.js";
const prefix = baseURL + "categories"; const prefix = baseURL + "categories";
@ -24,12 +24,12 @@ export const categoryAPI = {
}, },
async create(name) { async create(name) {
const response = await apiReq.post( const response = await apiReq.post(
categoryURLs.getAll, categoryURLs.getAll,
{ name: name }, { name: name },
function() { return i18n.t('category.category-creation-failed'); }, () => i18n.t("category.category-creation-failed"),
function() { return i18n.t('category.category-created'); } () => i18n.t("category.category-created")
); );
if(response) { if (response) {
store.dispatch("requestCategories"); store.dispatch("requestCategories");
return response.data; return response.data;
} }
@ -40,10 +40,10 @@ export const categoryAPI = {
}, },
async update(name, newName, overrideRequest = false) { async update(name, newName, overrideRequest = false) {
const response = await apiReq.put( const response = await apiReq.put(
categoryURLs.updateCategory(name), categoryURLs.updateCategory(name),
{ name: newName }, { name: newName },
function() { return i18n.t('category.category-update-failed'); }, () => i18n.t("category.category-update-failed"),
function() { return i18n.t('category.category-updated'); } () => i18n.t("category.category-updated")
); );
if (response && !overrideRequest) { if (response && !overrideRequest) {
store.dispatch("requestCategories"); store.dispatch("requestCategories");
@ -54,8 +54,8 @@ export const categoryAPI = {
const response = await apiReq.delete( const response = await apiReq.delete(
categoryURLs.deleteCategory(category), categoryURLs.deleteCategory(category),
null, null,
function() { return i18n.t('category.category-deletion-failed'); }, () => i18n.t("category.category-deletion-failed"),
function() { return i18n.t('category.category-deleted'); } () => i18n.t("category.category-deleted")
); );
if (response && !overrideRequest) { if (response && !overrideRequest) {
store.dispatch("requestCategories"); store.dispatch("requestCategories");
@ -85,12 +85,12 @@ export const tagAPI = {
}, },
async create(name) { async create(name) {
const response = await apiReq.post( const response = await apiReq.post(
tagURLs.getAll, tagURLs.getAll,
{ name: name }, { name: name },
function() { return i18n.t('tag.tag-creation-failed'); }, () => i18n.t("tag.tag-creation-failed"),
function() { return i18n.t('tag.tag-created'); } () => i18n.t("tag.tag-created")
); );
if(response) { if (response) {
store.dispatch("requestTags"); store.dispatch("requestTags");
return response.data; return response.data;
} }
@ -101,13 +101,13 @@ export const tagAPI = {
}, },
async update(name, newName, overrideRequest = false) { async update(name, newName, overrideRequest = false) {
const response = await apiReq.put( const response = await apiReq.put(
tagURLs.updateTag(name), tagURLs.updateTag(name),
{ name: newName }, { name: newName },
function() { return i18n.t('tag.tag-update-failed'); }, () => i18n.t("tag.tag-update-failed"),
function() { return i18n.t('tag.tag-updated'); } () => i18n.t("tag.tag-updated")
); );
if(response) { if (response) {
if (!overrideRequest) { if (!overrideRequest) {
store.dispatch("requestTags"); store.dispatch("requestTags");
} }
@ -118,10 +118,10 @@ export const tagAPI = {
const response = await apiReq.delete( const response = await apiReq.delete(
tagURLs.deleteTag(tag), tagURLs.deleteTag(tag),
null, null,
function() { return i18n.t('tag.tag-deletion-failed'); }, () => i18n.t("tag.tag-deletion-failed"),
function() { return i18n.t('tag.tag-deleted'); } () => i18n.t("tag.tag-deleted")
); );
if(response) { if (response) {
if (!overrideRequest) { if (!overrideRequest) {
store.dispatch("requestTags"); store.dispatch("requestTags");
} }

View File

@ -1,6 +1,6 @@
import { baseURL } from "./api-utils"; import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils"; import { apiReq } from "./api-utils";
import i18n from '@/i18n.js'; import i18n from "@/i18n.js";
const groupPrefix = baseURL + "groups"; const groupPrefix = baseURL + "groups";
const groupsURLs = { const groupsURLs = {
@ -12,18 +12,18 @@ const groupsURLs = {
}; };
function deleteErrorText(response) { function deleteErrorText(response) {
switch(response.data.detail) { switch (response.data.detail) {
case 'GROUP_WITH_USERS': case "GROUP_WITH_USERS":
return i18n.t('group.cannot-delete-group-with-users'); return i18n.t("group.cannot-delete-group-with-users");
case 'GROUP_NOT_FOUND': case "GROUP_NOT_FOUND":
return i18n.t('group.group-not-found'); return i18n.t("group.group-not-found");
case 'DEFAULT_GROUP': case "DEFAULT_GROUP":
return i18n.t('group.cannot-delete-default-group'); return i18n.t("group.cannot-delete-default-group");
default: default:
return i18n.t('group.group-deletion-failed'); return i18n.t("group.group-deletion-failed");
} }
} }
@ -36,33 +36,27 @@ export const groupAPI = {
return apiReq.post( return apiReq.post(
groupsURLs.create, groupsURLs.create,
{ name: name }, { name: name },
function() { return i18n.t('group.user-group-creation-failed'); }, () => i18n.t("group.user-group-creation-failed"),
function() { return i18n.t('group.user-group-created'); } () => i18n.t("group.user-group-created")
); );
}, },
delete(id) { delete(id) {
return apiReq.delete( return apiReq.delete(groupsURLs.delete(id), null, deleteErrorText, function() {
groupsURLs.delete(id), return i18n.t("group.group-deleted");
null, });
deleteErrorText,
function() { return i18n.t('group.group-deleted'); }
);
}, },
async current() { async current() {
const response = await apiReq.get( const response = await apiReq.get(groupsURLs.current, null, null);
groupsURLs.current, if (response) {
null,
null);
if(response) {
return response.data; return response.data;
} }
}, },
update(data) { update(data) {
return apiReq.put( return apiReq.put(
groupsURLs.update(data.id), groupsURLs.update(data.id),
data, data,
function() { return i18n.t('group.error-updating-group'); }, () => i18n.t("group.error-updating-group"),
function() { return i18n.t('settings.group-settings-updated'); } () => i18n.t("settings.group-settings-updated")
); );
}, },
}; };

View File

@ -1,6 +1,6 @@
import { baseURL } from "./api-utils"; import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils"; import { apiReq } from "./api-utils";
import i18n from '@/i18n.js'; import i18n from "@/i18n.js";
const prefix = baseURL + "meal-plans/"; const prefix = baseURL + "meal-plans/";
@ -18,10 +18,10 @@ const mealPlanURLs = {
export const mealplanAPI = { export const mealplanAPI = {
create(postBody) { create(postBody) {
return apiReq.post( return apiReq.post(
mealPlanURLs.create, mealPlanURLs.create,
postBody, postBody,
function() { return i18n.t('meal-plan.mealplan-creation-failed')}, () => i18n.t("meal-plan.mealplan-creation-failed"),
function() { return i18n.t('meal-plan.mealplan-created'); } () => i18n.t("meal-plan.mealplan-created")
); );
}, },
@ -41,19 +41,20 @@ export const mealplanAPI = {
}, },
delete(id) { delete(id) {
return apiReq.delete(mealPlanURLs.delete(id), return apiReq.delete(
mealPlanURLs.delete(id),
null, null,
function() { return i18n.t('meal-plan.mealplan-deletion-failed'); }, () => i18n.t("meal-plan.mealplan-deletion-failed"),
function() { return i18n.t('meal-plan.mealplan-deleted'); } () => i18n.t("meal-plan.mealplan-deleted")
); );
}, },
update(id, body) { update(id, body) {
return apiReq.put( return apiReq.put(
mealPlanURLs.update(id), mealPlanURLs.update(id),
body, body,
function() { return i18n.t('meal-plan.mealplan-update-failed'); }, () => i18n.t("meal-plan.mealplan-update-failed"),
function() { return i18n.t('meal-plan.mealplan-updated'); } () => i18n.t("meal-plan.mealplan-updated")
); );
}, },

View File

@ -1,7 +1,7 @@
import { baseURL } from "./api-utils"; import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils"; import { apiReq } from "./api-utils";
import { store } from "../store"; import { store } from "../store";
import i18n from '@/i18n.js'; import i18n from "@/i18n.js";
const migrationBase = baseURL + "migrations"; const migrationBase = baseURL + "migrations";
@ -21,8 +21,8 @@ export const migrationAPI = {
const response = await apiReq.delete( const response = await apiReq.delete(
migrationURLs.delete(folder, file), migrationURLs.delete(folder, file),
null, null,
function() { return i18n.t('general.file-folder-not-found'); }, () => i18n.t("general.file-folder-not-found"),
function() { return i18n.t('migration.migration-data-removed'); } () => i18n.t("migration.migration-data-removed")
); );
return response; return response;
}, },

View File

@ -1,7 +1,7 @@
import { baseURL } from "./api-utils"; import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils"; import { apiReq } from "./api-utils";
import { store } from "../store"; import { store } from "../store";
import i18n from '@/i18n.js'; import i18n from "@/i18n.js";
const prefix = baseURL + "recipes/"; const prefix = baseURL + "recipes/";
@ -27,10 +27,10 @@ export const recipeAPI = {
*/ */
async createByURL(recipeURL) { async createByURL(recipeURL) {
const response = await apiReq.post( const response = await apiReq.post(
recipeURLs.createByURL, recipeURLs.createByURL,
{ url: recipeURL }, { url: recipeURL },
function() { return i18n.t('recipe.recipe-creation-failed'); }, () => i18n.t("recipe.recipe-creation-failed"),
function() { return i18n.t('recipe.recipe-created'); } () => i18n.t("recipe.recipe-created")
); );
store.dispatch("requestRecentRecipes"); store.dispatch("requestRecentRecipes");
@ -38,19 +38,16 @@ export const recipeAPI = {
}, },
async getAllByCategory(categories) { async getAllByCategory(categories) {
let response = await apiReq.post( let response = await apiReq.post(recipeURLs.allRecipesByCategory, categories);
recipeURLs.allRecipesByCategory,
categories
);
return response.data; return response.data;
}, },
async create(recipeData) { async create(recipeData) {
const response = await apiReq.post( const response = await apiReq.post(
recipeURLs.create, recipeURLs.create,
recipeData, recipeData,
function() { return i18n.t('recipe.recipe-creation-failed'); }, () => i18n.t("recipe.recipe-creation-failed"),
function() { return i18n.t('recipe.recipe-created'); } () => i18n.t("recipe.recipe-created")
); );
store.dispatch("requestRecentRecipes"); store.dispatch("requestRecentRecipes");
return response.data; return response.data;
@ -67,18 +64,20 @@ export const recipeAPI = {
formData.append("extension", fileObject.name.split(".").pop()); formData.append("extension", fileObject.name.split(".").pop());
let successMessage = null; let successMessage = null;
if(!overrideSuccessMsg) { if (!overrideSuccessMsg) {
successMessage = function() { return overrideSuccessMsg ? null : i18n.t('recipe.recipe-image-updated'); }; successMessage = function() {
return overrideSuccessMsg ? null : i18n.t("recipe.recipe-image-updated");
};
} }
return apiReq.put( return apiReq.put(
recipeURLs.updateImage(recipeSlug), recipeURLs.updateImage(recipeSlug),
formData, formData,
function() { return i18n.t('general.image-upload-failed'); }, () => i18n.t("general.image-upload-failed"),
successMessage successMessage
); );
}, },
async createAsset(recipeSlug, fileObject, name, icon) { async createAsset(recipeSlug, fileObject, name, icon) {
const fd = new FormData(); const fd = new FormData();
fd.append("file", fileObject); fd.append("file", fileObject);
@ -88,24 +87,24 @@ export const recipeAPI = {
let response = apiReq.post(recipeURLs.createAsset(recipeSlug), fd); let response = apiReq.post(recipeURLs.createAsset(recipeSlug), fd);
return response; return response;
}, },
updateImagebyURL(slug, url) { updateImagebyURL(slug, url) {
return apiReq.post( return apiReq.post(
recipeURLs.updateImage(slug), recipeURLs.updateImage(slug),
{ url: url }, { url: url },
function() { return i18n.t('general.image-upload-failed'); }, () => i18n.t("general.image-upload-failed"),
function() { return i18n.t('recipe.recipe-image-updated'); } () => i18n.t("recipe.recipe-image-updated")
); );
}, },
async update(data) { async update(data) {
let response = await apiReq.put( let response = await apiReq.put(
recipeURLs.update(data.slug), recipeURLs.update(data.slug),
data, data,
function() { return i18n.t('recipe.recipe-update-failed'); }, () => i18n.t("recipe.recipe-update-failed"),
function() { return i18n.t('recipe.recipe-updated'); } () => i18n.t("recipe.recipe-updated")
); );
if(response) { if (response) {
store.dispatch("patchRecipe", response.data); store.dispatch("patchRecipe", response.data);
return response.data.slug; // ! Temporary until I rewrite to refresh page without additional request return response.data.slug; // ! Temporary until I rewrite to refresh page without additional request
} }
@ -117,13 +116,15 @@ export const recipeAPI = {
return response.data; return response.data;
}, },
delete(recipeSlug) { async delete(recipeSlug) {
return apiReq.delete( const response = await apiReq.delete(
recipeURLs.delete(recipeSlug), recipeURLs.delete(recipeSlug),
null, null,
function() { return i18n.t('recipe.unable-to-delete-recipe'); }, () => i18n.t("recipe.unable-to-delete-recipe"),
function() { return i18n.t('recipe.recipe-deleted'); } () => i18n.t("recipe.recipe-deleted")
); );
store.dispatch("dropRecipe", response.data);
return response;
}, },
async allSummary(start = 0, limit = 9999) { async allSummary(start = 0, limit = 9999) {

View File

@ -1,6 +1,6 @@
import { baseURL } from "./api-utils"; import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils"; import { apiReq } from "./api-utils";
import i18n from '@/i18n.js'; import i18n from "@/i18n.js";
const signUpPrefix = baseURL + "users/sign-ups"; const signUpPrefix = baseURL + "users/sign-ups";
@ -18,24 +18,27 @@ export const signupAPI = {
}, },
async createToken(data) { async createToken(data) {
let response = await apiReq.post( let response = await apiReq.post(
signUpURLs.createToken, signUpURLs.createToken,
data, data,
function() { return i18n.t('signup.sign-up-link-creation-failed'); }, () => i18n.t("signup.sign-up-link-creation-failed"),
function() { return i18n.t('signup.sign-up-link-created'); } () => i18n.t("signup.sign-up-link-created")
); );
return response.data; return response.data;
}, },
async deleteToken(token) { async deleteToken(token) {
return await apiReq.delete(signUpURLs.deleteToken(token), return await apiReq.delete(
null, signUpURLs.deleteToken(token),
function() { return i18n.t('signup.sign-up-token-deletion-failed'); }, null,
function() { return i18n.t('signup.sign-up-token-deleted'); } () => i18n.t("signup.sign-up-token-deletion-failed"),
() => i18n.t("signup.sign-up-token-deleted")
); );
}, },
async createUser(token, data) { async createUser(token, data) {
return apiReq.post(signUpURLs.createUser(token), data, return apiReq.post(
function() { return i18n.t('user.you-are-not-allowed-to-create-a-user'); }, signUpURLs.createUser(token),
function() { return i18n.t('user.user-created'); } data,
() => i18n.t("user.you-are-not-allowed-to-create-a-user"),
() => i18n.t("user.user-created")
); );
}, },
}; };

View File

@ -1,7 +1,7 @@
import { baseURL } from "./api-utils"; import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils"; import { apiReq } from "./api-utils";
import { store } from "@/store"; import { store } from "@/store";
import i18n from '@/i18n.js'; import i18n from "@/i18n.js";
const settingsBase = baseURL + "site-settings"; const settingsBase = baseURL + "site-settings";
@ -21,12 +21,12 @@ export const siteSettingsAPI = {
async update(body) { async update(body) {
const response = await apiReq.put( const response = await apiReq.put(
settingsURLs.updateSiteSettings, settingsURLs.updateSiteSettings,
body, body,
function() { return i18n.t('settings.settings-update-failed'); }, () => i18n.t("settings.settings-update-failed"),
function() { return i18n.t('settings.settings-updated'); } () => i18n.t("settings.settings-updated")
); );
if(response) { if (response) {
store.dispatch("requestSiteSettings"); store.dispatch("requestSiteSettings");
} }
return response; return response;
@ -44,10 +44,10 @@ export const siteSettingsAPI = {
createPage(body) { createPage(body) {
return apiReq.post( return apiReq.post(
settingsURLs.customPages, settingsURLs.customPages,
body, body,
function() { return i18n.t('page.page-creation-failed'); }, () => i18n.t("page.page-creation-failed"),
function() { return i18n.t('page.new-page-created'); } () => i18n.t("page.new-page-created")
); );
}, },
@ -55,25 +55,26 @@ export const siteSettingsAPI = {
return await apiReq.delete( return await apiReq.delete(
settingsURLs.customPage(id), settingsURLs.customPage(id),
null, null,
function() { return i18n.t('page.page-deletion-failed'); }, () => i18n.t("page.page-deletion-failed"),
function() { return i18n.t('page.page-deleted'); }); () => i18n.t("page.page-deleted")
);
}, },
updatePage(body) { updatePage(body) {
return apiReq.put( return apiReq.put(
settingsURLs.customPage(body.id), settingsURLs.customPage(body.id),
body, body,
function() { return i18n.t('page.page-update-failed'); }, () => i18n.t("page.page-update-failed"),
function() { return i18n.t('page.page-updated'); } () => i18n.t("page.page-updated")
); );
}, },
async updateAllPages(allPages) { async updateAllPages(allPages) {
let response = await apiReq.put( let response = await apiReq.put(
settingsURLs.customPages, settingsURLs.customPages,
allPages, allPages,
function() { return i18n.t('page.pages-update-failed'); }, () => i18n.t("page.pages-update-failed"),
function() { return i18n.t('page.pages-updated'); } () => i18n.t("page.pages-updated")
); );
return response; return response;
}, },

View File

@ -1,6 +1,6 @@
import { baseURL } from "./api-utils"; import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils"; import { apiReq } from "./api-utils";
import i18n from '@/i18n.js'; import i18n from "@/i18n.js";
const prefix = baseURL + "themes"; const prefix = baseURL + "themes";
@ -25,10 +25,11 @@ export const themeAPI = {
async create(postBody) { async create(postBody) {
return await apiReq.post( return await apiReq.post(
settingsURLs.createTheme, settingsURLs.createTheme,
postBody, postBody,
function() { return i18n.t('settings.theme.error-creating-theme-see-log-file'); }, () => i18n.t("settings.theme.error-creating-theme-see-log-file"),
function() { return i18n.t('settings.theme.theme-saved'); }); () => i18n.t("settings.theme.theme-saved")
);
}, },
update(themeName, colors) { update(themeName, colors) {
@ -37,18 +38,19 @@ export const themeAPI = {
colors: colors, colors: colors,
}; };
return apiReq.put( return apiReq.put(
settingsURLs.updateTheme(themeName), settingsURLs.updateTheme(themeName),
body, body,
function() { return i18n.t('settings.theme.error-updating-theme'); }, () => i18n.t("settings.theme.error-updating-theme"),
function() { return i18n.t('settings.theme.theme-updated'); }); () => i18n.t("settings.theme.theme-updated")
);
}, },
delete(themeName) { delete(themeName) {
return apiReq.delete( return apiReq.delete(
settingsURLs.deleteTheme(themeName), settingsURLs.deleteTheme(themeName),
null, null,
function() { return i18n.t('settings.theme.error-deleting-theme'); }, () => i18n.t("settings.theme.error-deleting-theme"),
function() { return i18n.t('settings.theme.theme-deleted'); } () => i18n.t("settings.theme.theme-deleted")
); );
}, },
}; };

View File

@ -1,5 +1,5 @@
import { apiReq } from "./api-utils"; import { apiReq } from "./api-utils";
import i18n from '@/i18n.js'; import i18n from "@/i18n.js";
export const utilsAPI = { export const utilsAPI = {
// import { api } from "@/api"; // import { api } from "@/api";
@ -9,8 +9,8 @@ export const utilsAPI = {
return apiReq.post( return apiReq.post(
url, url,
fileObject, fileObject,
function() { return i18n.t('general.failure-uploading-file'); }, () => i18n.t("general.failure-uploading-file"),
function() { return i18n.t('general.file-uploaded'); } () => i18n.t("general.file-uploaded")
); );
}, },
}; };

View File

@ -1,7 +1,7 @@
import { baseURL } from "./api-utils"; import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils"; import { apiReq } from "./api-utils";
import axios from "axios"; import axios from "axios";
import i18n from '@/i18n.js'; import i18n from "@/i18n.js";
const authPrefix = baseURL + "auth"; const authPrefix = baseURL + "auth";
const userPrefix = baseURL + "users"; const userPrefix = baseURL + "users";
@ -19,22 +19,19 @@ const usersURLs = {
}; };
function deleteErrorText(response) { function deleteErrorText(response) {
switch(response.data.detail) { switch (response.data.detail) {
case 'SUPER_USER': case "SUPER_USER":
return i18n.t('user.error-cannot-delete-super-user'); return i18n.t("user.error-cannot-delete-super-user");
default: default:
return i18n.t('user.you-are-not-allowed-to-delete-this-user'); return i18n.t("user.you-are-not-allowed-to-delete-this-user");
} }
} }
export const userAPI = { export const userAPI = {
async login(formData) { async login(formData) {
let response = await apiReq.post( let response = await apiReq.post(authURLs.token, formData, null, function() {
authURLs.token, return i18n.t("user.user-successfully-logged-in");
formData, });
null,
function() { return i18n.t('user.user-successfully-logged-in'); }
);
return response; return response;
}, },
async refresh() { async refresh() {
@ -49,10 +46,10 @@ export const userAPI = {
}, },
create(user) { create(user) {
return apiReq.post( return apiReq.post(
usersURLs.users, usersURLs.users,
user, user,
function() { return i18n.t('user.user-creation-failed'); }, () => i18n.t("user.user-creation-failed"),
function() { return i18n.t('user.user-created'); } () => i18n.t("user.user-created")
); );
}, },
async self() { async self() {
@ -65,35 +62,32 @@ export const userAPI = {
}, },
update(user) { update(user) {
return apiReq.put( return apiReq.put(
usersURLs.userID(user.id), usersURLs.userID(user.id),
user, user,
function() { return i18n.t('user.user-update-failed'); }, () => i18n.t("user.user-update-failed"),
function() { return i18n.t('user.user-updated'); } () => i18n.t("user.user-updated")
); );
}, },
changePassword(id, password) { changePassword(id, password) {
return apiReq.put( return apiReq.put(
usersURLs.password(id), usersURLs.password(id),
password, password,
function() { return i18n.t('user.existing-password-does-not-match'); }, () => i18n.t("user.existing-password-does-not-match"),
function() { return i18n.t('user.password-updated'); } () => i18n.t("user.password-updated")
);
},
delete(id) {
return apiReq.delete(
usersURLs.userID(id),
null,
deleteErrorText,
function() { return i18n.t('user.user-deleted'); }
); );
}, },
delete(id) {
return apiReq.delete(usersURLs.userID(id), null, deleteErrorText, function() {
return i18n.t("user.user-deleted");
});
},
resetPassword(id) { resetPassword(id) {
return apiReq.put( return apiReq.put(
usersURLs.resetPassword(id), usersURLs.resetPassword(id),
null, null,
function() { return i18n.t('user.password-reset-failed'); }, () => i18n.t("user.password-reset-failed"),
function() { return i18n.t('user.password-has-been-reset-to-the-default-password'); } () => i18n.t("user.password-has-been-reset-to-the-default-password")
); );
}, },
}; };

View File

@ -31,11 +31,7 @@
</v-chip> </v-chip>
</template> </template>
<template v-slot:append-outer=""> <template v-slot:append-outer="">
<NewCategoryTagDialog <NewCategoryTagDialog v-if="showAdd" :tag-dialog="tagSelector" @created-item="pushToItem" />
v-if="showAdd"
:tag-dialog="tagSelector"
@created-item="pushToItem"
/>
</template> </template>
</v-autocomplete> </v-autocomplete>
</template> </template>
@ -90,7 +86,7 @@ export default {
computed: { computed: {
inputLabel() { inputLabel() {
if (!this.showLabel) return null; if (!this.showLabel) return null;
return this.tagSelector ? this.$t('tag.tags') : this.$t('recipe.categories'); return this.tagSelector ? this.$t("tag.tags") : this.$t("recipe.categories");
}, },
activeItems() { activeItems() {
let ItemObjects = []; let ItemObjects = [];
@ -125,5 +121,4 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -3,21 +3,9 @@
<div class="text-center"> <div class="text-center">
<h3>{{ buttonText }}</h3> <h3>{{ buttonText }}</h3>
</div> </div>
<v-text-field <v-text-field v-model="color" hide-details class="ma-0 pa-0" solo v-show="$vuetify.breakpoint.mdAndUp">
v-model="color"
hide-details
class="ma-0 pa-0"
solo
v-show="$vuetify.breakpoint.mdAndUp"
>
<template v-slot:append> <template v-slot:append>
<v-menu <v-menu v-model="menu" top nudge-bottom="105" nudge-left="16" :close-on-content-click="false">
v-model="menu"
top
nudge-bottom="105"
nudge-left="16"
:close-on-content-click="false"
>
<template v-slot:activator="{ on }"> <template v-slot:activator="{ on }">
<div :style="swatchStyle" v-on="on" swatches-max-height="300" /> <div :style="swatchStyle" v-on="on" swatches-max-height="300" />
</template> </template>
@ -30,13 +18,7 @@
</template> </template>
</v-text-field> </v-text-field>
<div class="text-center" v-show="$vuetify.breakpoint.smAndDown"> <div class="text-center" v-show="$vuetify.breakpoint.smAndDown">
<v-menu <v-menu v-model="menu" top nudge-bottom="105" nudge-left="16" :close-on-content-click="false">
v-model="menu"
top
nudge-bottom="105"
nudge-left="16"
:close-on-content-click="false"
>
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-chip label :color="`${color}`" dark v-bind="attrs" v-on="on"> <v-chip label :color="`${color}`" dark v-bind="attrs" v-on="on">
{{ color }} {{ color }}
@ -88,5 +70,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -1,8 +1,5 @@
<template> <template>
<v-date-picker <v-date-picker :first-day-of-week="firstDayOfWeek" v-on="$listeners"></v-date-picker>
:first-day-of-week="firstDayOfWeek"
v-on="$listeners"
></v-date-picker>
</template> </template>
<script> <script>
@ -11,7 +8,7 @@ import { api } from "@/api";
export default { export default {
data() { data() {
return { return {
firstDayOfWeek: 0, firstDayOfWeek: 0,
}; };
}, },
mounted() { mounted() {
@ -24,8 +21,7 @@ export default {
this.firstDayOfWeek = settings.firstDayOfWeek; this.firstDayOfWeek = settings.firstDayOfWeek;
}, },
}, },
} };
</script> </script>
<style> <style></style>
</style>

View File

@ -45,4 +45,4 @@ export default {
}, },
}, },
}; };
</script> </script>

View File

@ -1,11 +1,5 @@
<template> <template>
<v-dialog <v-dialog ref="dialog" v-model="modal2" :return-value.sync="time" persistent width="290px">
ref="dialog"
v-model="modal2"
:return-value.sync="time"
persistent
width="290px"
>
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-text-field <v-text-field
v-model="time" v-model="time"
@ -18,8 +12,8 @@
</template> </template>
<v-time-picker v-if="modal2" v-model="time" full-width> <v-time-picker v-if="modal2" v-model="time" full-width>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn text color="primary" @click="modal2 = false"> {{$t('general.cancel')}} </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-btn text color="primary" @click="saveTime"> {{ $t("general.ok") }} </v-btn>
</v-time-picker> </v-time-picker>
</v-dialog> </v-dialog>
</template> </template>
@ -42,7 +36,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
.v-text-field{ .v-text-field {
max-width: 300px; max-width: 300px;
} }
</style> </style>

View File

@ -43,5 +43,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -63,34 +63,30 @@ export default {
}), }),
computed: { computed: {
importHeaders() { importHeaders() {
return [ return [
{ {
text: this.$t('general.status'), text: this.$t("general.status"),
value: "status", value: "status",
}, },
{ {
text: this.$t('general.name'), text: this.$t("general.name"),
align: "start", align: "start",
sortable: true, sortable: true,
value: "name", value: "name",
}, },
{ {
text: this.$t('general.exception'), text: this.$t("general.exception"),
value: "data-table-expand", value: "data-table-expand",
align: "center" align: "center",
}, },
] ];
}, },
recipeNumbers() { recipeNumbers() {
return this.calculateNumbers(this.$t("general.recipes"), this.recipeData); return this.calculateNumbers(this.$t("general.recipes"), this.recipeData);
}, },
settingsNumbers() { settingsNumbers() {
return this.calculateNumbers( return this.calculateNumbers(this.$t("general.settings"), this.settingsData);
this.$t("general.settings"),
this.settingsData
);
}, },
themeNumbers() { themeNumbers() {
return this.calculateNumbers(this.$t("general.themes"), this.themeData); return this.calculateNumbers(this.$t("general.themes"), this.themeData);
@ -115,14 +111,7 @@ export default {
]; ];
}, },
allTables() { allTables() {
return [ return [this.recipeData, this.themeData, this.settingsData, this.pageData, this.userData, this.groupData];
this.recipeData,
this.themeData,
this.settingsData,
this.pageData,
this.userData,
this.groupData,
];
}, },
}, },
@ -150,5 +139,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -25,5 +25,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -5,14 +5,7 @@
<v-icon large left v-if="!loading"> <v-icon large left v-if="!loading">
mdi-account mdi-account
</v-icon> </v-icon>
<v-progress-circular <v-progress-circular v-else indeterminate color="white" large class="mr-2"> </v-progress-circular>
v-else
indeterminate
color="white"
large
class="mr-2"
>
</v-progress-circular>
<v-toolbar-title class="headline">{{ $t("user.login") }}</v-toolbar-title> <v-toolbar-title class="headline">{{ $t("user.login") }}</v-toolbar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
</v-app-bar> </v-app-bar>
@ -42,11 +35,7 @@
@click:append="showPassword = !showPassword" @click:append="showPassword = !showPassword"
></v-text-field> ></v-text-field>
<v-card-actions> <v-card-actions>
<v-btn <v-btn v-if="options.isLoggingIn" color="primary" block="block" type="submit"
v-if="options.isLoggingIn"
color="primary"
block="block"
type="submit"
>{{ $t("user.sign-in") }} >{{ $t("user.sign-in") }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@ -108,5 +97,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -5,21 +5,14 @@
<v-icon large left v-if="!loading"> <v-icon large left v-if="!loading">
mdi-account mdi-account
</v-icon> </v-icon>
<v-progress-circular <v-progress-circular v-else indeterminate color="white" large class="mr-2"> </v-progress-circular>
v-else <v-toolbar-title class="headline">
indeterminate {{ $t("signup.sign-up") }}
color="white"
large
class="mr-2"
>
</v-progress-circular>
<v-toolbar-title class="headline">
{{$t('signup.sign-up')}}
</v-toolbar-title> </v-toolbar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
</v-app-bar> </v-app-bar>
<v-card-text> <v-card-text>
{{$t('signup.welcome-to-mealie')}} {{ $t("signup.welcome-to-mealie") }}
<v-divider class="mt-3"></v-divider> <v-divider class="mt-3"></v-divider>
<v-form ref="signUpForm" @submit.prevent="signUp"> <v-form ref="signUpForm" @submit.prevent="signUp">
<v-text-field <v-text-field
@ -58,24 +51,16 @@
:label="$t('user.password')" :label="$t('user.password')"
:type="showPassword ? 'text' : 'password'" :type="showPassword ? 'text' : 'password'"
:append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'" :append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
:rules="[ :rules="[user.password === user.passwordConfirm || $t('user.password-must-match')]"
user.password === user.passwordConfirm || $t('user.password-must-match'),
]"
@click:append="showPassword = !showPassword" @click:append="showPassword = !showPassword"
></v-text-field> ></v-text-field>
<v-card-actions> <v-card-actions>
<v-btn <v-btn v-if="options.isLoggingIn" dark color="primary" block="block" type="submit">
v-if="options.isLoggingIn" {{ $t("signup.sign-up") }}
dark
color="primary"
block="block"
type="submit"
>
{{$t('signup.sign-up')}}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
<v-alert dense v-if="error" outlined class="mt-3 mb-0" type="error"> <v-alert dense v-if="error" outlined class="mt-3 mb-0" type="error">
{{$t('signup.error-signing-up')}} {{ $t("signup.error-signing-up") }}
</v-alert> </v-alert>
</v-form> </v-form>
</v-card-text> </v-card-text>
@ -138,14 +123,11 @@ export default {
this.$router.push("/"); this.$router.push("/");
} }
} }
this.loading = false; this.loading = false;
}, },
}, },
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -1,22 +1,10 @@
<template> <template>
<v-row> <v-row>
<SearchDialog ref="mealselect" @select="setSlug" /> <SearchDialog ref="mealselect" @select="setSlug" />
<v-col <v-col cols="12" sm="12" md="6" lg="4" xl="3" v-for="(meal, index) in value" :key="index">
cols="12"
sm="12"
md="6"
lg="4"
xl="3"
v-for="(meal, index) in value"
:key="index"
>
<v-hover v-slot="{ hover }" :open-delay="50"> <v-hover v-slot="{ hover }" :open-delay="50">
<v-card :class="{ 'on-hover': hover }" :elevation="hover ? 12 : 2"> <v-card :class="{ 'on-hover': hover }" :elevation="hover ? 12 : 2">
<v-img <v-img height="200" :src="getImage(meal.slug)" @click="openSearch(index)"></v-img>
height="200"
:src="getImage(meal.slug)"
@click="openSearch(index)"
></v-img>
<v-card-title class="my-n3 mb-n6"> <v-card-title class="my-n3 mb-n6">
{{ $d(new Date(meal.date.split("-")), "short") }} {{ $d(new Date(meal.date.split("-")), "short") }}
</v-card-title> </v-card-title>
@ -63,5 +51,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -44,5 +44,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -180,9 +180,7 @@ export default {
}); });
}, },
processTime(index) { processTime(index) {
let dateText = new Date( let dateText = new Date(this.actualStartDate.valueOf() + 1000 * 3600 * 24 * index);
this.actualStartDate.valueOf() + 1000 * 3600 * 24 * index
);
return dateText; return dateText;
}, },
getDate(index) { getDate(index) {
@ -215,22 +213,10 @@ export default {
return this.$d(date); return this.$d(date);
}, },
getNextDayOfTheWeek(dayName, excludeToday = true, refDate = new Date()) { getNextDayOfTheWeek(dayName, excludeToday = true, refDate = new Date()) {
const dayOfWeek = [ const dayOfWeek = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"].indexOf(dayName.slice(0, 3).toLowerCase());
"sun",
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
].indexOf(dayName.slice(0, 3).toLowerCase());
if (dayOfWeek < 0) return; if (dayOfWeek < 0) return;
refDate.setUTCHours(0, 0, 0, 0); refDate.setUTCHours(0, 0, 0, 0);
refDate.setDate( refDate.setDate(refDate.getDate() + +!!excludeToday + ((dayOfWeek + 7 - refDate.getDay() - +!!excludeToday) % 7));
refDate.getDate() +
+!!excludeToday +
((dayOfWeek + 7 - refDate.getDay() - +!!excludeToday) % 7)
);
return refDate; return refDate;
}, },
setQuickWeek() { setQuickWeek() {
@ -245,5 +231,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -3,28 +3,21 @@
<v-dialog v-model="dialog" width="650"> <v-dialog v-model="dialog" width="650">
<v-card> <v-card>
<v-card-title class="headline"> <v-card-title class="headline">
{{$t('meal-plan.shopping-list')}} {{ $t("meal-plan.shopping-list") }}
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn text color="accent" @click="group = !group"> <v-btn text color="accent" @click="group = !group">
{{$t('meal-plan.group')}} {{ $t("meal-plan.group") }}
</v-btn> </v-btn>
</v-card-title> </v-card-title>
<v-divider></v-divider> <v-divider></v-divider>
<v-card-text v-if="group == false"> <v-card-text v-if="group == false">
<v-list <v-list dense v-for="(recipe, index) in ingredients" :key="`${index}-recipe`">
dense
v-for="(recipe, index) in ingredients"
:key="`${index}-recipe`"
>
<v-subheader>{{ recipe.name }} </v-subheader> <v-subheader>{{ recipe.name }} </v-subheader>
<v-divider></v-divider> <v-divider></v-divider>
<v-list-item-group color="primary"> <v-list-item-group color="primary">
<v-list-item <v-list-item v-for="(item, i) in recipe.recipe_ingredient" :key="i">
v-for="(item, i) in recipe.recipe_ingredient"
:key="i"
>
<v-list-item-content> <v-list-item-content>
<v-list-item-title v-text="item"></v-list-item-title> <v-list-item-title v-text="item"></v-list-item-title>
</v-list-item-content> </v-list-item-content>
@ -104,8 +97,4 @@ export default {
}; };
</script> </script>
<style></style>
<style>
</style>

View File

@ -0,0 +1,151 @@
<template>
<div class="text-center">
<ConfirmationDialog
:title="$t('recipe.delete-recipe')"
:message="$t('recipe.delete-confirmation')"
color="error"
icon="mdi-alert-circle"
ref="deleteRecipieConfirm"
v-on:confirm="deleteRecipe()"
/>
<v-menu offset-y top left>
<template v-slot:activator="{ on, attrs }">
<v-btn color="primary" icon dark v-bind="attrs" v-on="on" @click.prevent>
<v-icon>{{ menuIcon }}</v-icon>
</v-btn>
</template>
<v-list dense>
<v-list-item
v-for="(item, index) in loggedIn ? userMenu : defaultMenu"
:key="index"
@click="menuAction(item.action)"
>
<v-list-item-icon>
<v-icon v-text="item.icon" :color="item.color"></v-icon>
</v-list-item-icon>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
</template>
<script>
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog.vue";
import { api } from "@/api";
export default {
components: {
ConfirmationDialog,
},
props: {
slug: {
type: String,
},
menuIcon: {
default: "mdi-dots-vertical",
},
},
computed: {
loggedIn() {
return this.$store.getters.getIsLoggedIn;
},
baseURL() {
return window.location.origin;
},
recipeURL() {
return `${this.baseURL}/recipe/${this.slug}`;
},
defaultMenu() {
return [
{
title: this.$t("general.download"),
icon: "mdi-download",
color: "accent",
action: "download",
},
{
title: this.$t("general.link"),
icon: "mdi-content-copy",
color: "accent",
action: "share",
},
];
},
userMenu() {
return [
{
title: this.$t("general.delete"),
icon: "mdi-delete",
color: "error",
action: "delete",
},
{
title: this.$t("general.edit"),
icon: "mdi-square-edit-outline",
color: "accent",
action: "edit",
},
...this.defaultMenu,
];
},
},
data() {
return {
loading: true,
};
},
methods: {
async menuAction(action) {
this.loading = true;
switch (action) {
case "delete":
this.$refs.deleteRecipieConfirm.open();
break;
case "share":
this.updateClipboard();
break;
case "edit":
this.$router.push(`/recipe/${this.slug}` + "?edit=true");
break;
case "download":
await this.downloadJson();
break;
default:
break;
}
this.loading = false;
},
async deleteRecipe() {
await api.recipes.delete(this.slug);
},
updateClipboard() {
const copyText = this.recipeURL;
navigator.clipboard.writeText(copyText).then(
() => console.log("Copied", copyText),
() => console.log("Copied Failed", copyText)
);
},
async downloadJson() {
const recipe = await api.recipes.requestDetails(this.slug);
this.downloadString(JSON.stringify(recipe, "", 4), "text/json", recipe.slug+'.json');
},
downloadString(text, fileType, fileName) {
let blob = new Blob([text], { type: fileType });
let a = document.createElement("a");
a.download = fileName;
a.href = URL.createObjectURL(blob);
a.dataset.downloadurl = [fileType, a.download, a.href].join(":");
a.style.display = "none";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(function() {
URL.revokeObjectURL(a.href);
}, 1500);
},
},
};
</script>

View File

@ -18,14 +18,7 @@
<template v-slot:extension> <template v-slot:extension>
<v-col></v-col> <v-col></v-col>
<div v-if="open"> <div v-if="open">
<v-btn <v-btn class="mr-2" fab dark small color="error" @click="deleteRecipeConfrim">
class="mr-2"
fab
dark
small
color="error"
@click="deleteRecipeConfrim"
>
<v-icon>mdi-delete</v-icon> <v-icon>mdi-delete</v-icon>
</v-btn> </v-btn>
@ -101,5 +94,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -1,30 +1,14 @@
<template> <template>
<v-card <v-card :ripple="false" class="mx-auto" hover :to="`/recipe/${slug}`" @click="$emit('selected')">
class="mx-auto"
hover
:to="`/recipe/${slug}`"
@click="$emit('selected')"
>
<v-list-item three-line> <v-list-item three-line>
<v-list-item-avatar <v-list-item-avatar tile size="125" color="grey" class="v-mobile-img rounded-sm my-0 ml-n4">
tile
size="125"
color="grey"
class="v-mobile-img rounded-sm my-0 ml-n4"
>
<v-img :src="getImage(slug)" lazy-src=""></v-img <v-img :src="getImage(slug)" lazy-src=""></v-img
></v-list-item-avatar> ></v-list-item-avatar>
<v-list-item-content> <v-list-item-content>
<v-list-item-title class=" mb-1">{{ name }}</v-list-item-title> <v-list-item-title class=" mb-1">{{ name }} </v-list-item-title>
<v-list-item-subtitle> {{ description }} </v-list-item-subtitle> <v-list-item-subtitle> {{ description }} </v-list-item-subtitle>
<div class="d-flex justify-center align-center"> <div class="d-flex justify-center align-center">
<RecipeChips <RecipeChips :items="tags" :title="false" :limit="1" :small="true" :isCategory="false" />
:items="tags"
:title="false"
:limit="1"
:small="true"
:isCategory="false"
/>
<v-rating <v-rating
color="secondary" color="secondary"
class="ml-auto" class="ml-auto"
@ -34,6 +18,7 @@
size="15" size="15"
:value="rating" :value="rating"
></v-rating> ></v-rating>
<ContextMenu :slug="slug" menu-icon="mdi-dots-horizontal" />
</div> </div>
</v-list-item-content> </v-list-item-content>
</v-list-item> </v-list-item>
@ -42,10 +27,12 @@
<script> <script>
import RecipeChips from "@/components/Recipe/RecipeViewer/RecipeChips"; import RecipeChips from "@/components/Recipe/RecipeViewer/RecipeChips";
import ContextMenu from "@/components/Recipe/ContextMenu";
import { api } from "@/api"; import { api } from "@/api";
export default { export default {
components: { components: {
RecipeChips, RecipeChips,
ContextMenu,
}, },
props: { props: {
name: String, name: String,
@ -96,4 +83,4 @@ export default {
.text-top { .text-top {
align-self: start !important; align-self: start !important;
} }
</style> </style>

View File

@ -11,10 +11,7 @@
<v-icon v-text="item.icon"></v-icon> <v-icon v-text="item.icon"></v-icon>
</v-list-item-icon> </v-list-item-icon>
<v-list-item-content> <v-list-item-content>
<v-list-item-title <v-list-item-title class="pl-2" v-text="item.name"></v-list-item-title>
class="pl-2"
v-text="item.name"
></v-list-item-title>
</v-list-item-content> </v-list-item-content>
<v-list-item-action> <v-list-item-action>
<v-btn <v-btn
@ -36,30 +33,16 @@
</v-card> </v-card>
<div class="d-flex ml-auto mt-2"> <div class="d-flex ml-auto mt-2">
<v-spacer></v-spacer> <v-spacer></v-spacer>
<base-dialog <base-dialog @submit="addAsset" :title="$t('recipe.new-asset')" :title-icon="newAsset.icon">
@submit="addAsset"
:title="$t('recipe.new-asset')"
:title-icon="newAsset.icon"
>
<template v-slot:open="{ open }"> <template v-slot:open="{ open }">
<v-btn color="secondary" dark @click="open" v-if="edit"> <v-btn color="secondary" dark @click="open" v-if="edit">
<v-icon>mdi-plus</v-icon> <v-icon>mdi-plus</v-icon>
</v-btn> </v-btn>
</template> </template>
<v-card-text class="pt-2"> <v-card-text class="pt-2">
<v-text-field <v-text-field dense v-model="newAsset.name" :label="$t('general.name')"></v-text-field>
dense
v-model="newAsset.name"
:label="$t('general.name')"
></v-text-field>
<div class="d-flex justify-space-between"> <div class="d-flex justify-space-between">
<v-select <v-select dense :prepend-icon="newAsset.icon" v-model="newAsset.icon" :items="iconOptions" class="mr-2">
dense
:prepend-icon="newAsset.icon"
v-model="newAsset.icon"
:items="iconOptions"
class="mr-2"
>
<template v-slot:item="{ item }"> <template v-slot:item="{ item }">
<v-list-item-avatar> <v-list-item-avatar>
<v-icon class="mr-auto"> <v-icon class="mr-auto">
@ -69,12 +52,7 @@
{{ item }} {{ item }}
</template> </template>
</v-select> </v-select>
<TheUploadBtn <TheUploadBtn @uploaded="setFileObject" :post="false" file-name="file" :text-btn="false" />
@uploaded="setFileObject"
:post="false"
file-name="file"
:text-btn="false"
/>
</div> </div>
{{ fileObject.name }} {{ fileObject.name }}
</v-card-text> </v-card-text>
@ -109,13 +87,7 @@ export default {
name: "", name: "",
icon: "mdi-file", icon: "mdi-file",
}, },
iconOptions: [ iconOptions: ["mdi-file", "mdi-file-pdf-box", "mdi-file-image", "mdi-code-json", "mdi-silverware-fork-knife"],
"mdi-file",
"mdi-file-pdf-box",
"mdi-file-image",
"mdi-code-json",
"mdi-silverware-fork-knife",
],
menu: [ menu: [
{ {
title: "Link 1", title: "Link 1",
@ -156,5 +128,4 @@ export default {
}; };
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

@ -2,24 +2,17 @@
<div class="text-center"> <div class="text-center">
<v-dialog v-model="dialog" width="600"> <v-dialog v-model="dialog" width="600">
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn <v-btn color="secondary lighten-2" dark v-bind="attrs" v-on="on" @click="inputText = ''">
{{ $t("new-recipe.bulk-add") }}
color="secondary lighten-2"
dark
v-bind="attrs"
v-on="on"
@click="inputText = ''"
>
{{$t('new-recipe.bulk-add')}}
</v-btn> </v-btn>
</template> </template>
<v-card> <v-card>
<v-card-title class="headline"> {{$t('new-recipe.bulk-add')}} </v-card-title> <v-card-title class="headline"> {{ $t("new-recipe.bulk-add") }} </v-card-title>
<v-card-text> <v-card-text>
<p> <p>
{{$t('new-recipe.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> </p>
<v-textarea v-model="inputText"> </v-textarea> <v-textarea v-model="inputText"> </v-textarea>
</v-card-text> </v-card-text>
@ -28,7 +21,7 @@
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="success" text @click="save"> {{$t('general.save')}} </v-btn> <v-btn color="success" text @click="save"> {{ $t("general.save") }} </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>
@ -61,4 +54,4 @@ export default {
}, },
}, },
}; };
</script> </script>

View File

@ -9,34 +9,17 @@
<v-card-title> {{ $t("recipe.api-extras") }} </v-card-title> <v-card-title> {{ $t("recipe.api-extras") }} </v-card-title>
<v-card-text :key="formKey"> <v-card-text :key="formKey">
<v-row <v-row align="center" v-for="(value, key, index) in extras" :key="index">
align="center"
v-for="(value, key, index) in extras"
:key="index"
>
<v-col cols="12" sm="1"> <v-col cols="12" sm="1">
<v-btn <v-btn fab text x-small color="white" elevation="0" @click="removeExtra(key)">
fab
text
x-small
color="white"
elevation="0"
@click="removeExtra(key)"
>
<v-icon color="error">mdi-delete</v-icon> <v-icon color="error">mdi-delete</v-icon>
</v-btn> </v-btn>
</v-col> </v-col>
<v-col cols="12" md="3" sm="6"> <v-col cols="12" md="3" sm="6">
<v-text-field <v-text-field :label="$t('recipe.object-key')" :value="key" @input="updateKey(index)"> </v-text-field>
:label="$t('recipe.object-key')"
:value="key"
@input="updateKey(index)"
>
</v-text-field>
</v-col> </v-col>
<v-col cols="12" md="8" sm="6"> <v-col cols="12" md="8" sm="6">
<v-text-field :label="$t('recipe.object-value')" v-model="extras[key]"> <v-text-field :label="$t('recipe.object-value')" v-model="extras[key]"> </v-text-field>
</v-text-field>
</v-col> </v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>
@ -74,9 +57,8 @@ export default {
dialog: false, dialog: false,
formKey: 1, formKey: 1,
rules: { rules: {
required: (v) => !!v || this.$i18n.t("recipe.key-name-required"), required: v => !!v || this.$i18n.t("recipe.key-name-required"),
whiteSpace: (v) => whiteSpace: v => !v || v.split(" ").length <= 1 || this.$i18n.t("recipe.no-white-space-allowed"),
!v || v.split(" ").length <= 1 || this.$i18n.t("recipe.no-white-space-allowed"),
}, },
}; };
}, },
@ -100,5 +82,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -25,19 +25,9 @@
</v-card-title> </v-card-title>
<v-card-text class="mt-n5"> <v-card-text class="mt-n5">
<div> <div>
<v-text-field <v-text-field :label="$t('general.url')" class="pt-5" clearable v-model="url">
:label="$t('general.url')"
class="pt-5"
clearable
v-model="url"
>
<template v-slot:append-outer> <template v-slot:append-outer>
<v-btn <v-btn class="ml-2" color="primary" @click="getImageFromURL" :loading="loading">
class="ml-2"
color="primary"
@click="getImageFromURL"
:loading="loading"
>
{{ $t("general.get") }} {{ $t("general.get") }}
</v-btn> </v-btn>
</template> </template>
@ -80,5 +70,4 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -12,7 +12,7 @@
<v-card> <v-card>
<v-card-title class="py-2"> <v-card-title class="py-2">
<div> <div>
{{$t('recipe.recipe-settings')}} {{ $t("recipe.recipe-settings") }}
</div> </div>
</v-card-title> </v-card-title>
<v-divider class="mx-2"></v-divider> <v-divider class="mx-2"></v-divider>
@ -43,17 +43,16 @@ export default {
computed: { computed: {
labels() { labels() {
return { return {
public: this.$t('recipe.public-recipe'), public: this.$t("recipe.public-recipe"),
showNutrition: this.$t('recipe.show-nutrition-values'), showNutrition: this.$t("recipe.show-nutrition-values"),
showAssets: this.$t('recipe.show-assets'), showAssets: this.$t("recipe.show-assets"),
landscapeView: this.$t('recipe.landscape-view-coming-soon'), landscapeView: this.$t("recipe.landscape-view-coming-soon"),
}; };
} },
}, },
methods: {}, methods: {},
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -2,18 +2,9 @@
<div> <div>
<h2 class="mb-4">{{ $t("recipe.ingredients") }}</h2> <h2 class="mb-4">{{ $t("recipe.ingredients") }}</h2>
<div v-if="edit"> <div v-if="edit">
<draggable <draggable :value="value" @input="updateIndex" @start="drag = true" @end="drag = false" handle=".handle">
:value="value"
@input="updateIndex"
@start="drag = true"
@end="drag = false"
handle=".handle"
>
<transition-group type="transition" :name="!drag ? 'flip-list' : null"> <transition-group type="transition" :name="!drag ? 'flip-list' : null">
<div <div v-for="(ingredient, index) in value" :key="generateKey('ingredient', index)">
v-for="(ingredient, index) in value"
:key="generateKey('ingredient', index)"
>
<v-row align="center"> <v-row align="center">
<v-textarea <v-textarea
class="mr-2" class="mr-2"
@ -28,12 +19,7 @@
<template slot="append-outer"> <template slot="append-outer">
<v-icon class="handle">mdi-arrow-up-down</v-icon> <v-icon class="handle">mdi-arrow-up-down</v-icon>
</template> </template>
<v-icon <v-icon class="mr-n1" slot="prepend" color="error" @click="removeByIndex(value, index)">
class="mr-n1"
slot="prepend"
color="error"
@click="removeByIndex(value, index)"
>
mdi-delete mdi-delete
</v-icon> </v-icon>
</v-textarea> </v-textarea>
@ -56,20 +42,10 @@
:key="generateKey('ingredient', index)" :key="generateKey('ingredient', index)"
@click="toggleChecked(index)" @click="toggleChecked(index)"
> >
<v-checkbox <v-checkbox hide-details :value="checked[index]" class="pt-0 my-auto py-auto" color="secondary"> </v-checkbox>
hide-details
:value="checked[index]"
class="pt-0 my-auto py-auto"
color="secondary"
>
</v-checkbox>
<v-list-item-content> <v-list-item-content>
<vue-markdown <vue-markdown class="ma-0 pa-0 text-subtitle-1 dense-markdown" :source="ingredient"> </vue-markdown>
class="ma-0 pa-0 text-subtitle-1 dense-markdown"
:source="ingredient"
>
</vue-markdown>
</v-list-item-content> </v-list-item-content>
</v-list-item> </v-list-item>
</div> </div>
@ -130,8 +106,8 @@ export default {
}; };
</script> </script>
<style > <style>
.dense-markdown p { .dense-markdown p {
margin: auto !important; margin: auto !important;
} }
</style> </style>

View File

@ -3,13 +3,7 @@
<h2 class="mb-4">{{ $t("recipe.instructions") }}</h2> <h2 class="mb-4">{{ $t("recipe.instructions") }}</h2>
<div> <div>
<div v-for="(step, index) in value" :key="index"> <div v-for="(step, index) in value" :key="index">
<v-app-bar <v-app-bar v-if="showTitleEditor[index]" class="primary mx-1 mt-6" dark dense rounded>
v-if="showTitleEditor[index]"
class="primary mx-1 mt-6"
dark
dense
rounded
>
<v-toolbar-title class="headline" v-if="!edit"> <v-toolbar-title class="headline" v-if="!edit">
<v-app-bar-title v-text="step.title"> </v-app-bar-title> <v-app-bar-title v-text="step.title"> </v-app-bar-title>
</v-toolbar-title> </v-toolbar-title>
@ -46,16 +40,8 @@
<v-icon size="24" color="error">mdi-delete</v-icon> <v-icon size="24" color="error">mdi-delete</v-icon>
</v-btn> </v-btn>
{{ $t("recipe.step-index", { step: index + 1 }) }} {{ $t("recipe.step-index", { step: index + 1 }) }}
<v-btn <v-btn v-if="edit" text color="primary" class="ml-auto" @click="toggleShowTitle(index)">
v-if="edit" {{ !showTitleEditor[index] ? "Insert Section" : "Remove Section" }}
text
color="primary"
class="ml-auto"
@click="toggleShowTitle(index)"
>
{{
!showTitleEditor[index] ? "Insert Section" : "Remove Section"
}}
</v-btn> </v-btn>
</v-card-title> </v-card-title>
<v-card-text v-if="edit"> <v-card-text v-if="edit">
@ -144,5 +130,4 @@ export default {
}; };
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

@ -1,36 +1,17 @@
<template> <template>
<div v-if="value.length > 0 || edit"> <div v-if="value.length > 0 || edit">
<h2 class="my-4">{{ $t("recipe.note") }}</h2> <h2 class="my-4">{{ $t("recipe.note") }}</h2>
<v-card <v-card class="mt-1" v-for="(note, index) in value" :key="generateKey('note', index)">
class="mt-1"
v-for="(note, index) in value"
:key="generateKey('note', index)"
>
<div v-if="edit"> <div v-if="edit">
<v-card-text> <v-card-text>
<v-row align="center"> <v-row align="center">
<v-btn <v-btn fab x-small color="white" class="mr-2" elevation="0" @click="removeByIndex(value, index)">
fab
x-small
color="white"
class="mr-2"
elevation="0"
@click="removeByIndex(value, index)"
>
<v-icon color="error">mdi-delete</v-icon> <v-icon color="error">mdi-delete</v-icon>
</v-btn> </v-btn>
<v-text-field <v-text-field :label="$t('recipe.title')" v-model="value[index]['title']"></v-text-field>
:label="$t('recipe.title')"
v-model="value[index]['title']"
></v-text-field>
</v-row> </v-row>
<v-textarea <v-textarea auto-grow :placeholder="$t('recipe.note')" v-model="value[index]['text']"> </v-textarea>
auto-grow
:placeholder="$t('recipe.note')"
v-model="value[index]['text']"
>
</v-textarea>
</v-card-text> </v-card-text>
</div> </div>
<div v-else> <div v-else>
@ -83,5 +64,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -97,5 +97,4 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -0,0 +1,62 @@
<template>
<div @click.prevent>
<v-rating
:readonly="!loggedIn"
color="secondary"
background-color="secondary lighten-3"
length="5"
:dense="small ? true : undefined"
:size="small ? 15 : undefined"
hover
v-model="rating"
:value="value"
@input="updateRating"
@click="updateRating"
></v-rating>
</div>
</template>
<script>
import { api } from "@/api";
export default {
props: {
emitOnly: {
default: false,
},
name: String,
slug: String,
value: Number,
small: {
default: false,
},
},
mounted() {
this.rating = this.value;
},
data() {
return {
rating: 0,
};
},
computed: {
loggedIn() {
return this.$store.getters.getIsLoggedIn;
},
},
methods: {
updateRating(val) {
if (this.emitOnly) {
this.$emit("input", val);
return;
}
api.recipes.patch({
name: this.name,
slug: this.slug,
rating: val,
});
},
},
};
</script>
<style lang="scss" scoped></style>

View File

@ -9,11 +9,7 @@
> >
<v-img height="200" :src="getImage(slug)"> <v-img height="200" :src="getImage(slug)">
<v-expand-transition v-if="description"> <v-expand-transition v-if="description">
<div <div v-if="hover" class="d-flex transition-fast-in-fast-out secondary v-card--reveal " style="height: 100%;">
v-if="hover"
class="d-flex transition-fast-in-fast-out secondary v-card--reveal "
style="height: 100%;"
>
<v-card-text class="v-card--text-show white--text"> <v-card-text class="v-card--text-show white--text">
{{ description | truncate(300) }} {{ description | truncate(300) }}
</v-card-text> </v-card-text>
@ -27,23 +23,10 @@
</v-card-title> </v-card-title>
<v-card-actions> <v-card-actions>
<v-rating <Rating :value="rating" :name="name" :slug="slug" :small="true" />
class="mr-2 my-auto"
color="secondary"
background-color="secondary lighten-3"
dense
length="5"
size="15"
:value="rating"
></v-rating>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<RecipeChips <RecipeChips :items="tags" :title="false" :limit="2" :small="true" :isCategory="false" />
:items="tags" <ContextMenu :slug="slug" />
:title="false"
:limit="2"
:small="true"
:isCategory="false"
/>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-hover> </v-hover>
@ -51,10 +34,14 @@
<script> <script>
import RecipeChips from "@/components/Recipe/RecipeViewer/RecipeChips"; import RecipeChips from "@/components/Recipe/RecipeViewer/RecipeChips";
import ContextMenu from "@/components/Recipe/ContextMenu";
import Rating from "@/components/Recipe/Parts/Rating";
import { api } from "@/api"; import { api } from "@/api";
export default { export default {
components: { components: {
RecipeChips, RecipeChips,
ContextMenu,
Rating,
}, },
props: { props: {
name: String, name: String,
@ -96,4 +83,4 @@ export default {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
</style> </style>

View File

@ -2,70 +2,27 @@
<v-form ref="form"> <v-form ref="form">
<v-card-text> <v-card-text>
<v-row dense> <v-row dense>
<ImageUploadBtn <ImageUploadBtn class="my-1" @upload="uploadImage" :slug="value.slug" @refresh="$emit('upload')" />
class="my-1" <SettingsMenu class="my-1 mx-1" @upload="uploadImage" :value="value.settings" />
@upload="uploadImage"
:slug="value.slug"
@refresh="$emit('upload')"
/>
<SettingsMenu
class="my-1 mx-1"
@upload="uploadImage"
:value="value.settings"
/>
</v-row> </v-row>
<v-row dense> <v-row dense>
<v-col> <v-col>
<v-text-field <v-text-field :label="$t('recipe.total-time')" v-model="value.totalTime"></v-text-field>
:label="$t('recipe.total-time')"
v-model="value.totalTime"
></v-text-field>
</v-col> </v-col>
<v-col <v-col><v-text-field :label="$t('recipe.prep-time')" v-model="value.prepTime"></v-text-field></v-col>
><v-text-field <v-col><v-text-field :label="$t('recipe.perform-time')" v-model="value.performTime"></v-text-field></v-col>
:label="$t('recipe.prep-time')"
v-model="value.prepTime"
></v-text-field
></v-col>
<v-col
><v-text-field
:label="$t('recipe.perform-time')"
v-model="value.performTime"
></v-text-field
></v-col>
</v-row> </v-row>
<v-text-field <v-text-field class="my-3" :label="$t('recipe.recipe-name')" v-model="value.name" :rules="[existsRule]">
class="my-3"
:label="$t('recipe.recipe-name')"
v-model="value.name"
:rules="[existsRule]"
>
</v-text-field> </v-text-field>
<v-textarea <v-textarea auto-grow min-height="100" :label="$t('recipe.description')" v-model="value.description">
auto-grow
min-height="100"
:label="$t('recipe.description')"
v-model="value.description"
>
</v-textarea> </v-textarea>
<div class="my-2"></div> <div class="my-2"></div>
<v-row dense disabled> <v-row dense disabled>
<v-col sm="4"> <v-col sm="4">
<v-text-field <v-text-field :label="$t('recipe.servings')" v-model="value.recipeYield" class="rounded-sm"> </v-text-field>
:label="$t('recipe.servings')"
v-model="value.recipeYield"
class="rounded-sm"
>
</v-text-field>
</v-col> </v-col>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-rating <Rating v-model="value.rating" :emit-only="true" />
class="mr-2 align-end"
color="secondary darken-1"
background-color="secondary lighten-3"
length="5"
v-model="value.rating"
></v-rating>
</v-row> </v-row>
<v-row> <v-row>
<v-col cols="12" sm="12" md="4" lg="4"> <v-col cols="12" sm="12" md="4" lg="4">
@ -104,11 +61,7 @@
</div> </div>
<Notes :edit="true" v-model="value.notes" /> <Notes :edit="true" v-model="value.notes" />
<v-text-field <v-text-field v-model="value.orgURL" class="mt-10" :label="$t('recipe.original-url')"></v-text-field>
v-model="value.orgURL"
class="mt-10"
:label="$t('recipe.original-url')"
></v-text-field>
</v-col> </v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>
@ -128,6 +81,7 @@ import Ingredients from "@/components/Recipe/Parts/Ingredients";
import Assets from "@/components/Recipe/Parts/Assets.vue"; import Assets from "@/components/Recipe/Parts/Assets.vue";
import Notes from "@/components/Recipe/Parts/Notes.vue"; import Notes from "@/components/Recipe/Parts/Notes.vue";
import SettingsMenu from "@/components/Recipe/Parts/Helpers/SettingsMenu.vue"; import SettingsMenu from "@/components/Recipe/Parts/Helpers/SettingsMenu.vue";
import Rating from "@/components/Recipe/Parts/Rating";
export default { export default {
components: { components: {
BulkAdd, BulkAdd,
@ -140,6 +94,7 @@ export default {
Assets, Assets,
Notes, Notes,
SettingsMenu, SettingsMenu,
Rating,
}, },
props: { props: {
value: Object, value: Object,
@ -181,4 +136,4 @@ export default {
.my-divider { .my-divider {
margin: 0 -1px; margin: 0 -1px;
} }
</style> </style>

View File

@ -3,35 +3,16 @@
<v-card flat class="d-print-none"> <v-card flat class="d-print-none">
<v-card-text> <v-card-text>
<v-row align="center" justify="center"> <v-row align="center" justify="center">
<v-btn <v-btn left color="accent lighten-1 " class="ma-1 image-action" @click="$emit('exit')">
left
color="accent lighten-1 "
class="ma-1 image-action"
@click="$emit('exit')"
>
<v-icon> mdi-arrow-left </v-icon> <v-icon> mdi-arrow-left </v-icon>
</v-btn> </v-btn>
<v-card flat class="text-center" align-center> <v-card flat class="text-center" align-center>
<v-card-text>Font Size</v-card-text> <v-card-text>Font Size</v-card-text>
<v-card-text> <v-card-text>
<v-btn <v-btn class="mx-2" fab dark x-small color="primary" @click="subtractFontSize">
class="mx-2"
fab
dark
x-small
color="primary"
@click="subtractFontSize"
>
<v-icon dark> mdi-minus </v-icon> <v-icon dark> mdi-minus </v-icon>
</v-btn> </v-btn>
<v-btn <v-btn class="mx-2" fab dark x-small color="primary" @click="addFontSize">
class="mx-2"
fab
dark
x-small
color="primary"
@click="addFontSize"
>
<v-icon dark> mdi-plus </v-icon> <v-icon dark> mdi-plus </v-icon>
</v-btn> </v-btn>
</v-card-text> </v-card-text>
@ -52,8 +33,7 @@
</v-card> </v-card>
</v-col> </v-col>
<v-col md="1" sm="1" justify-end> <v-col md="1" sm="1" justify-end>
<v-img :src="getImage(recipe.image)" max-height="200" max-width="300"> <v-img :src="getImage(recipe.image)" max-height="200" max-width="300"> </v-img>
</v-img>
</v-col> </v-col>
</v-row> </v-row>
</v-card> </v-card>
@ -104,37 +84,20 @@
<v-col cols="12"> <v-col cols="12">
<div v-if="recipe.categories[0]"> <div v-if="recipe.categories[0]">
<h2 class="mt-4">Categories</h2> <h2 class="mt-4">Categories</h2>
<v-chip <v-chip class="ma-1" color="primary" dark v-for="category in recipe.categories" :key="category">
class="ma-1"
color="primary"
dark
v-for="category in recipe.categories"
:key="category"
>
{{ category }} {{ category }}
</v-chip> </v-chip>
</div> </div>
<div v-if="recipe.tags[0]"> <div v-if="recipe.tags[0]">
<h2 class="mt-4">Tags</h2> <h2 class="mt-4">Tags</h2>
<v-chip <v-chip class="ma-1" color="primary" dark v-for="tag in recipe.tags" :key="tag">
class="ma-1"
color="primary"
dark
v-for="tag in recipe.tags"
:key="tag"
>
{{ tag }} {{ tag }}
</v-chip> </v-chip>
</div> </div>
<h2 v-if="recipe.notes[0]" class="my-2">Notes</h2> <h2 v-if="recipe.notes[0]" class="my-2">Notes</h2>
<v-card <v-card flat class="mt-1" v-for="(note, index) in recipe.notes" :key="generateKey('note', index)">
flat
class="mt-1"
v-for="(note, index) in recipe.notes"
:key="generateKey('note', index)"
>
<v-card-title> {{ note.title }}</v-card-title> <v-card-title> {{ note.title }}</v-card-title>
<v-card-text> <v-card-text>
{{ note.text }} {{ note.text }}
@ -196,4 +159,4 @@ export default {
.column-wrapper { .column-wrapper {
column-count: 2; column-count: 2;
} }
</style> </style>

View File

@ -35,31 +35,19 @@ export default {
}, },
computed: { computed: {
showCards() { showCards() {
return [this.prepTime, this.totalTime, this.performTime].some( return [this.prepTime, this.totalTime, this.performTime].some(x => !this.isEmpty(x));
x => !this.isEmpty(x)
);
}, },
allTimes() { allTimes() {
return [ return [this.validateTotalTime, this.validatePrepTime, this.validatePerformTime].filter(x => x !== null);
this.validateTotalTime,
this.validatePrepTime,
this.validatePerformTime,
].filter(x => x !== null);
}, },
validateTotalTime() { validateTotalTime() {
return !this.isEmpty(this.totalTime) return !this.isEmpty(this.totalTime) ? { name: this.$t("recipe.total-time"), value: this.totalTime } : null;
? { name: this.$t("recipe.total-time"), value: this.totalTime }
: null;
}, },
validatePrepTime() { validatePrepTime() {
return !this.isEmpty(this.prepTime) return !this.isEmpty(this.prepTime) ? { name: this.$t("recipe.prep-time"), value: this.prepTime } : null;
? { name: this.$t("recipe.prep-time"), value: this.prepTime }
: null;
}, },
validatePerformTime() { validatePerformTime() {
return !this.isEmpty(this.performTime) return !this.isEmpty(this.performTime) ? { name: this.$t("recipe.perform-time"), value: this.performTime } : null;
? { name: this.$t("recipe.perform-time"), value: this.performTime }
: null;
}, },
}, },
methods: { methods: {
@ -77,4 +65,4 @@ export default {
.custom-transparent { .custom-transparent {
opacity: 0.7; opacity: 0.7;
} }
</style> </style>

View File

@ -62,5 +62,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -21,13 +21,7 @@
{{ yields }} {{ yields }}
</v-btn> </v-btn>
</v-col> </v-col>
<v-rating <Rating :value="rating" :name="name" :slug="slug" />
class="mr-2 align-end static"
color="secondary darken-1"
background-color="secondary lighten-3"
length="5"
:value="rating"
></v-rating>
</v-row> </v-row>
<v-row> <v-row>
<v-col cols="12" sm="12" md="4" lg="4"> <v-col cols="12" sm="12" md="4" lg="4">
@ -56,11 +50,7 @@
<Assets :value="assets" :edit="false" :slug="slug" /> <Assets :value="assets" :edit="false" :slug="slug" />
</div> </div>
</v-col> </v-col>
<v-divider <v-divider v-if="medium" class="my-divider" :vertical="true"></v-divider>
v-if="medium"
class="my-divider"
:vertical="true"
></v-divider>
<v-col cols="12" sm="12" md="8" lg="8"> <v-col cols="12" sm="12" md="8" lg="8">
<Instructions :value="instructions" :edit="false" /> <Instructions :value="instructions" :edit="false" />
@ -100,6 +90,7 @@ import Nutrition from "@/components/Recipe/Parts/Nutrition";
import VueMarkdown from "@adapttive/vue-markdown"; import VueMarkdown from "@adapttive/vue-markdown";
import utils from "@/utils"; import utils from "@/utils";
import RecipeChips from "./RecipeChips"; import RecipeChips from "./RecipeChips";
import Rating from "@/components/Recipe/Parts/Rating";
import Notes from "@/components/Recipe/Parts/Notes"; import Notes from "@/components/Recipe/Parts/Notes";
import Ingredients from "@/components/Recipe/Parts/Ingredients"; import Ingredients from "@/components/Recipe/Parts/Ingredients";
import Instructions from "@/components/Recipe/Parts/Instructions.vue"; import Instructions from "@/components/Recipe/Parts/Instructions.vue";
@ -113,6 +104,7 @@ export default {
Nutrition, Nutrition,
Instructions, Instructions,
Assets, Assets,
Rating,
}, },
props: { props: {
name: String, name: String,
@ -154,4 +146,4 @@ export default {
.my-divider { .my-divider {
margin: 0 -1px; margin: 0 -1px;
} }
</style> </style>

View File

@ -47,5 +47,4 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -1,12 +1,7 @@
<template> <template>
<v-form ref="file"> <v-form ref="file">
<input ref="uploader" class="d-none" type="file" @change="onFileChanged" /> <input ref="uploader" class="d-none" type="file" @change="onFileChanged" />
<v-btn <v-btn :loading="isSelecting" @click="onButtonClick" color="accent" :text="textBtn">
:loading="isSelecting"
@click="onButtonClick"
color="accent"
:text="textBtn"
>
<v-icon left> {{ icon }}</v-icon> <v-icon left> {{ icon }}</v-icon>
{{ text ? text : defaultText }} {{ text ? text : defaultText }}
</v-btn> </v-btn>
@ -55,7 +50,7 @@ export default {
let formData = new FormData(); let formData = new FormData();
formData.append(this.fileName, this.file); formData.append(this.fileName, this.file);
if(await api.utils.uploadFile(this.url, formData)) { if (await api.utils.uploadFile(this.url, formData)) {
this.$emit(UPLOAD_EVENT); this.$emit(UPLOAD_EVENT);
} }
this.isSelecting = false; this.isSelecting = false;
@ -81,5 +76,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -22,14 +22,10 @@
</template> </template>
<v-list> <v-list>
<v-list-item @click="$emit('sort-recent')"> <v-list-item @click="$emit('sort-recent')">
<v-list-item-title>{{ <v-list-item-title>{{ $t("general.recent") }}</v-list-item-title>
$t("general.recent")
}}</v-list-item-title>
</v-list-item> </v-list-item>
<v-list-item @click="$emit('sort')"> <v-list-item @click="$emit('sort')">
<v-list-item-title>{{ <v-list-item-title>{{ $t("general.sort-alphabetically") }}</v-list-item-title>
$t("general.sort-alphabetically")
}}</v-list-item-title>
</v-list-item> </v-list-item>
</v-list> </v-list>
</v-menu> </v-menu>
@ -39,14 +35,7 @@
</v-card> </v-card>
<div v-if="recipes"> <div v-if="recipes">
<v-row v-if="!viewScale"> <v-row v-if="!viewScale">
<v-col <v-col :sm="6" :md="6" :lg="4" :xl="3" v-for="recipe in recipes.slice(0, cardLimit)" :key="recipe.name">
:sm="6"
:md="6"
:lg="4"
:xl="3"
v-for="recipe in recipes.slice(0, cardLimit)"
:key="recipe.name"
>
<RecipeCard <RecipeCard
:name="recipe.name" :name="recipe.name"
:description="recipe.description" :description="recipe.description"
@ -148,10 +137,7 @@ export default {
}, },
methods: { methods: {
bumpList() { bumpList() {
const newCardLimit = Math.min( const newCardLimit = Math.min(this.cardLimit + 20, this.effectiveHardLimit);
this.cardLimit + 20,
this.effectiveHardLimit
);
if (this.loading === false && newCardLimit > this.cardLimit) { if (this.loading === false && newCardLimit > this.cardLimit) {
this.setLoader(); this.setLoader();
@ -172,4 +158,4 @@ export default {
.transparent { .transparent {
opacity: 1; opacity: 1;
} }
</style> </style>

View File

@ -1,11 +1,7 @@
<template> <template>
<div> <div>
<slot name="open" v-bind="{ open }"> </slot> <slot name="open" v-bind="{ open }"> </slot>
<v-dialog <v-dialog v-model="dialog" :width="modalWidth + 'px'" :content-class="top ? 'top-dialog' : undefined">
v-model="dialog"
:width="modalWidth + 'px'"
:content-class="top ? 'top-dialog' : undefined"
>
<v-card class="pb-10" height="100%"> <v-card class="pb-10" height="100%">
<v-app-bar dark :color="color" class="mt-n1 mb-2"> <v-app-bar dark :color="color" class="mt-n1 mb-2">
<v-icon large left> <v-icon large left>
@ -14,20 +10,16 @@
<v-toolbar-title class="headline"> {{ title }} </v-toolbar-title> <v-toolbar-title class="headline"> {{ title }} </v-toolbar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
</v-app-bar> </v-app-bar>
<v-progress-linear <v-progress-linear v-if="loading" indeterminate color="primary"></v-progress-linear>
v-if="loading"
indeterminate
color="primary"
></v-progress-linear>
<slot> </slot> <slot> </slot>
<v-card-actions> <v-card-actions>
<slot name="card-actions"> <slot name="card-actions">
<v-btn text color="grey" @click="dialog = false"> <v-btn text color="grey" @click="dialog = false">
{{$t('general.cancel')}} {{ $t("general.cancel") }}
</v-btn> </v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="success" @click="submitEvent"> <v-btn color="success" @click="submitEvent">
{{$t('general.submit')}} {{ $t("general.submit") }}
</v-btn> </v-btn>
</slot> </slot>
</v-card-actions> </v-card-actions>
@ -84,4 +76,4 @@ export default {
.top-dialog { .top-dialog {
align-self: flex-start; align-self: flex-start;
} }
</style> </style>

View File

@ -1,4 +1,3 @@
<template> <template>
<v-dialog <v-dialog
v-model="dialog" v-model="dialog"
@ -7,17 +6,16 @@
@click:outside="cancel" @click:outside="cancel"
@keydown.esc="cancel" @keydown.esc="cancel"
> >
<template v-slot:activator="{}">
<slot v-bind="{ open }"> </slot>
</template>
<v-card> <v-card>
<v-app-bar v-if="Boolean(title)" :color="color" dense dark> <v-app-bar v-if="Boolean(title)" :color="color" dense dark>
<v-icon v-if="Boolean(icon)" left> {{ icon }}</v-icon> <v-icon v-if="Boolean(icon)" left> {{ icon }}</v-icon>
<v-toolbar-title v-text="title" /> <v-toolbar-title v-text="title" />
</v-app-bar> </v-app-bar>
<v-card-text <v-card-text v-show="!!message" class="pa-4 text--primary" v-html="message" />
v-show="!!message"
class="pa-4 text--primary"
v-html="message"
/>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
@ -35,6 +33,7 @@
<script> <script>
const CLOSE_EVENT = "close"; const CLOSE_EVENT = "close";
const OPEN_EVENT = "open"; const OPEN_EVENT = "open";
const CONFIRM_EVENT = "confirm";
/** /**
* ConfirmationDialog Component used to add a second validaion step to an action. * ConfirmationDialog Component used to add a second validaion step to an action.
* @version 1.0.1 * @version 1.0.1
@ -96,13 +95,9 @@ export default {
dialog: false, dialog: false,
}), }),
methods: { methods: {
/**
* Sets the modal to be visiable.
*/
open() { open() {
this.dialog = true; this.dialog = true;
}, },
/** /**
* Cancel button handler. * Cancel button handler.
*/ */
@ -129,7 +124,7 @@ export default {
* @event confirm * @event confirm
* @property {string} content content of the first prop passed to the event * @property {string} content content of the first prop passed to the event
*/ */
this.$emit("confirm"); this.$emit(CONFIRM_EVENT);
//Hide Modal //Hide Modal
this.dialog = false; this.dialog = false;
@ -138,5 +133,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -21,12 +21,7 @@
<v-card-title> </v-card-title> <v-card-title> </v-card-title>
<v-form @submit.prevent="select"> <v-form @submit.prevent="select">
<v-card-text> <v-card-text>
<v-text-field <v-text-field dense :label="inputLabel" v-model="itemName" :rules="[rules.required]"></v-text-field>
dense
:label="inputLabel"
v-model="itemName"
:rules="[rules.required]"
></v-text-field>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
@ -103,5 +98,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -61,9 +61,7 @@ export default {
try { try {
this.results = this.fuse.search(this.search.trim()); this.results = this.fuse.search(this.search.trim());
} catch { } catch {
this.results = this.rawData this.results = this.rawData.map(x => ({ item: x })).sort((a, b) => (a.name > b.name ? 1 : -1));
.map(x => ({ item: x }))
.sort((a, b) => (a.name > b.name ? 1 : -1));
} }
this.$emit(RESULTS_EVENT, this.results); this.$emit(RESULTS_EVENT, this.results);
@ -75,5 +73,4 @@ export default {
}; };
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

@ -1,11 +1,5 @@
<template> <template>
<v-menu <v-menu v-model="menuModel" readonly offset-y offset-overflow max-height="75vh">
v-model="menuModel"
readonly
offset-y
offset-overflow
max-height="75vh"
>
<template #activator="{ attrs }"> <template #activator="{ attrs }">
<v-text-field <v-text-field
ref="searchInput" ref="searchInput"
@ -33,12 +27,7 @@
</template> </template>
</v-text-field> </v-text-field>
</template> </template>
<v-card <v-card v-if="showResults" max-height="75vh" :max-width="maxWidth" scrollable>
v-if="showResults"
max-height="75vh"
:max-width="maxWidth"
scrollable
>
<v-card-text class="flex row mx-auto "> <v-card-text class="flex row mx-auto ">
<div class="mr-auto"> <div class="mr-auto">
Results Results
@ -56,22 +45,10 @@
<v-list-item-avatar> <v-list-item-avatar>
<v-img :src="getImage(item.item.slug)"></v-img> <v-img :src="getImage(item.item.slug)"></v-img>
</v-list-item-avatar> </v-list-item-avatar>
<v-list-item-content <v-list-item-content @click="showResults ? null : selected(item.item.slug, item.item.name)">
@click=" <v-list-item-title v-html="highlight(item.item.name)"> </v-list-item-title>
showResults ? null : selected(item.item.slug, item.item.name) <v-rating dense v-if="item.item.rating" :value="item.item.rating" size="12"> </v-rating>
" <v-list-item-subtitle v-html="highlight(item.item.description)"> </v-list-item-subtitle>
>
<v-list-item-title v-html="highlight(item.item.name)">
</v-list-item-title>
<v-rating
dense
v-if="item.item.rating"
:value="item.item.rating"
size="12"
>
</v-rating>
<v-list-item-subtitle v-html="highlight(item.item.description)">
</v-list-item-subtitle>
</v-list-item-content> </v-list-item-content>
</v-list-item> </v-list-item>
</v-list> </v-list>
@ -153,9 +130,7 @@ export default {
try { try {
this.result = this.fuse.search(this.search.trim()); this.result = this.fuse.search(this.search.trim());
} catch { } catch {
this.result = this.data this.result = this.data.map(x => ({ item: x })).sort((a, b) => (a.name > b.name ? 1 : -1));
.map(x => ({ item: x }))
.sort((a, b) => (a.name > b.name ? 1 : -1));
} }
this.$emit("results", this.result); this.$emit("results", this.result);
@ -173,10 +148,7 @@ export default {
if (!this.search) { if (!this.search) {
return string; return string;
} }
return string.replace( return string.replace(new RegExp(this.search, "gi"), match => `<mark>${match}</mark>`);
new RegExp(this.search, "gi"),
match => `<mark>${match}</mark>`
);
}, },
getImage(image) { getImage(image) {
return api.recipes.recipeTinyImage(image); return api.recipes.recipeTinyImage(image);
@ -221,4 +193,4 @@ export default {
&, & > * &, & > *
display: flex display: flex
flex-direction: column flex-direction: column
</style> </style>

View File

@ -1,12 +1,6 @@
<template> <template>
<div class="text-center "> <div class="text-center ">
<v-dialog <v-dialog v-model="dialog" width="600px" height="0" :fullscreen="isMobile" content-class="top-dialog">
v-model="dialog"
width="600px"
height="0"
:fullscreen="isMobile"
content-class="top-dialog"
>
<v-card> <v-card>
<v-app-bar dark color="primary lighten-1" rounded="0"> <v-app-bar dark color="primary lighten-1" rounded="0">
<SearchBar <SearchBar
@ -103,4 +97,4 @@ export default {
.top-dialog { .top-dialog {
align-self: flex-start; align-self: flex-start;
} }
</style> </style>

View File

@ -1,15 +1,7 @@
<template> <template>
<div> <div>
<TheSidebar ref="theSidebar" /> <TheSidebar ref="theSidebar" />
<v-app-bar <v-app-bar clipped-left dense app color="primary" dark class="d-print-none" :bottom="isMobile">
clipped-left
dense
app
color="primary"
dark
class="d-print-none"
:bottom="isMobile"
>
<v-btn icon @click="openSidebar"> <v-btn icon @click="openSidebar">
<v-icon> mdi-menu </v-icon> <v-icon> mdi-menu </v-icon>
</v-btn> </v-btn>
@ -36,7 +28,7 @@
<v-btn icon @click="$refs.recipeSearch.open()"> <v-btn icon @click="$refs.recipeSearch.open()">
<v-icon> mdi-magnify </v-icon> <v-icon> mdi-magnify </v-icon>
</v-btn> </v-btn>
<SearchDialog ref="recipeSearch"/> <SearchDialog ref="recipeSearch" />
</div> </div>
<TheSiteMenu /> <TheSiteMenu />
@ -90,5 +82,4 @@ export default {
}; };
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

@ -6,14 +6,7 @@
<v-icon large left v-if="!processing"> <v-icon large left v-if="!processing">
mdi-link mdi-link
</v-icon> </v-icon>
<v-progress-circular <v-progress-circular v-else indeterminate color="white" large class="mr-2"> </v-progress-circular>
v-else
indeterminate
color="white"
large
class="mr-2"
>
</v-progress-circular>
<v-toolbar-title class="headline"> <v-toolbar-title class="headline">
{{ $t("new-recipe.from-url") }} {{ $t("new-recipe.from-url") }}
@ -54,21 +47,9 @@
</v-form> </v-form>
</v-card> </v-card>
</v-dialog> </v-dialog>
<v-speed-dial <v-speed-dial v-model="fab" :open-on-hover="absolute" :fixed="absolute" :bottom="absolute" :right="absolute">
v-model="fab"
:open-on-hover="absolute"
:fixed="absolute"
:bottom="absolute"
:right="absolute"
>
<template v-slot:activator> <template v-slot:activator>
<v-btn <v-btn v-model="fab" :color="absolute ? 'accent' : 'white'" dark :icon="!absolute" :fab="absolute">
v-model="fab"
:color="absolute ? 'accent' : 'white'"
dark
:icon="!absolute"
:fab="absolute"
>
<v-icon> mdi-plus </v-icon> <v-icon> mdi-plus </v-icon>
</v-btn> </v-btn>
</template> </template>
@ -132,5 +113,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -4,11 +4,7 @@
<template v-slot:prepend> <template v-slot:prepend>
<v-list-item two-line v-if="isLoggedIn"> <v-list-item two-line v-if="isLoggedIn">
<v-list-item-avatar color="accent" class="white--text"> <v-list-item-avatar color="accent" class="white--text">
<img <img :src="userProfileImage" v-if="!hideImage" @error="hideImage = true" />
:src="userProfileImage"
v-if="!hideImage"
@error="hideImage = true"
/>
<div v-else> <div v-else>
{{ initials }} {{ initials }}
</div> </div>
@ -16,21 +12,14 @@
<v-list-item-content> <v-list-item-content>
<v-list-item-title> {{ user.fullName }}</v-list-item-title> <v-list-item-title> {{ user.fullName }}</v-list-item-title>
<v-list-item-subtitle> <v-list-item-subtitle> {{ user.admin ? "Admin" : "User" }}</v-list-item-subtitle>
{{ user.admin ? "Admin" : "User" }}</v-list-item-subtitle
>
</v-list-item-content> </v-list-item-content>
</v-list-item> </v-list-item>
</template> </template>
<v-divider></v-divider> <v-divider></v-divider>
<v-list nav dense> <v-list nav dense>
<v-list-item <v-list-item v-for="nav in effectiveMenu" :key="nav.title" link :to="nav.to">
v-for="nav in effectiveMenu"
:key="nav.title"
link
:to="nav.to"
>
<v-list-item-icon> <v-list-item-icon>
<v-icon>{{ nav.icon }}</v-icon> <v-icon>{{ nav.icon }}</v-icon>
</v-list-item-icon> </v-list-item-icon>
@ -228,15 +217,12 @@ export default {
this.showSidebar = false; this.showSidebar = false;
}, },
async getVersion() { async getVersion() {
let response = await axios.get( let response = await axios.get("https://api.github.com/repos/hay-kot/mealie/releases/latest", {
"https://api.github.com/repos/hay-kot/mealie/releases/latest", headers: {
{ "content-type": "application/json",
headers: { Authorization: null,
"content-type": "application/json", },
Authorization: null, });
},
}
);
this.latestVersion = response.data.tag_name; this.latestVersion = response.data.tag_name;
}, },
@ -250,4 +236,4 @@ export default {
bottom: 0 !important; bottom: 0 !important;
width: 100%; width: 100%;
} }
</style> </style>

View File

@ -1,15 +1,7 @@
<template> <template>
<div class="text-center"> <div class="text-center">
<LoginDialog ref="loginDialog" /> <LoginDialog ref="loginDialog" />
<v-menu <v-menu transition="slide-x-transition" bottom right offset-y offset-overflow open-on-hover close-delay="200">
transition="slide-x-transition"
bottom
right
offset-y
offset-overflow
open-on-hover
close-delay="200"
>
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn v-bind="attrs" v-on="on" icon> <v-btn v-bind="attrs" v-on="on" icon>
<v-icon>mdi-account</v-icon> <v-icon>mdi-account</v-icon>
@ -49,7 +41,7 @@ export default {
return [ return [
{ {
icon: "mdi-account", icon: "mdi-account",
title: this.$t('user.login'), title: this.$t("user.login"),
restricted: false, restricted: false,
login: true, login: true,
}, },
@ -83,7 +75,7 @@ export default {
nav: "/admin", nav: "/admin",
restricted: true, restricted: true,
}, },
] ];
}, },
filteredItems() { filteredItems() {
if (this.loggedIn) { if (this.loggedIn) {
@ -108,4 +100,4 @@ export default {
.menu-text { .menu-text {
text-align: left !important; text-align: left !important;
} }
</style> </style>

View File

@ -3,7 +3,6 @@ import VueI18n from "vue-i18n";
Vue.use(VueI18n); Vue.use(VueI18n);
function parseLocaleFiles(locales) { function parseLocaleFiles(locales) {
const messages = {}; const messages = {};
locales.keys().forEach(key => { locales.keys().forEach(key => {
@ -17,27 +16,18 @@ function parseLocaleFiles(locales) {
} }
function loadLocaleMessages() { function loadLocaleMessages() {
const locales = require.context( const locales = require.context("./locales/messages", true, /[A-Za-z0-9-_,\s]+\.json$/i);
"./locales/messages",
true,
/[A-Za-z0-9-_,\s]+\.json$/i
);
return parseLocaleFiles(locales); return parseLocaleFiles(locales);
} }
function loadDateTimeFormats() { function loadDateTimeFormats() {
const locales = require.context( const locales = require.context("./locales/dateTimeFormats", true, /[A-Za-z0-9-_,\s]+\.json$/i);
"./locales/dateTimeFormats",
true,
/[A-Za-z0-9-_,\s]+\.json$/i
);
return parseLocaleFiles(locales); return parseLocaleFiles(locales);
} }
export default new VueI18n({ export default new VueI18n({
locale: "en-US", locale: "en-US",
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || "en-US", fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || "en-US",
messages: loadLocaleMessages(), messages: loadLocaleMessages(),
dateTimeFormats: loadDateTimeFormats() dateTimeFormats: loadDateTimeFormats(),
}); });

View File

@ -55,6 +55,7 @@
"image-upload-failed": "Image upload failed", "image-upload-failed": "Image upload failed",
"import": "Import", "import": "Import",
"keyword": "Keyword", "keyword": "Keyword",
"link": "Link",
"monday": "Monday", "monday": "Monday",
"name": "Name", "name": "Name",
"no": "No", "no": "No",

View File

@ -22,7 +22,7 @@ const vueApp = new Vue({
}).$mount("#app"); }).$mount("#app");
// Truncate // Truncate
let truncate = function(text, length, clamp) { const truncate = function(text, length, clamp) {
clamp = clamp || "..."; clamp = clamp || "...";
let node = document.createElement("div"); let node = document.createElement("div");
node.innerHTML = text; node.innerHTML = text;
@ -30,7 +30,7 @@ let truncate = function(text, length, clamp) {
return content.length > length ? content.slice(0, length) + clamp : content; return content.length > length ? content.slice(0, length) + clamp : content;
}; };
let titleCase = function(value) { const titleCase = function(value) {
return value.replace(/(?:^|\s|-)\S/g, x => x.toUpperCase()); return value.replace(/(?:^|\s|-)\S/g, x => x.toUpperCase());
}; };

View File

@ -1,21 +1,13 @@
export const validators = { export const validators = {
data() { data() {
return { return {
emailRule: v => emailRule: v => !v || /^[^@\s]+@[^@\s.]+.[^@.\s]+$/.test(v) || this.$t("user.e-mail-must-be-valid"),
!v ||
/^[^@\s]+@[^@\s.]+.[^@.\s]+$/.test(v) ||
this.$t("user.e-mail-must-be-valid"),
existsRule: value => !!value || this.$t("general.field-required"), existsRule: value => !!value || this.$t("general.field-required"),
minRule: v => minRule: v => v.length >= 8 || this.$t("user.use-8-characters-or-more-for-your-password"),
v.length >= 8 ||
this.$t("user.use-8-characters-or-more-for-your-password"),
whiteSpace: v => whiteSpace: v => !v || v.split(" ").length <= 1 || this.$t("recipe.no-white-space-allowed"),
!v ||
v.split(" ").length <= 1 ||
this.$t("recipe.no-white-space-allowed"),
}; };
}, },
}; };

View File

@ -5,9 +5,9 @@
<v-col> <v-col>
<v-card height=""> <v-card height="">
<v-card-text> <v-card-text>
<h1>{{$t('404.page-not-found')}}</h1> <h1>{{ $t("404.page-not-found") }}</h1>
</v-card-text> </v-card-text>
<v-btn text block @click="$router.push('/')"> {{$t('404.take-me-home')}} </v-btn> <v-btn text block @click="$router.push('/')"> {{ $t("404.take-me-home") }} </v-btn>
</v-card> </v-card>
</v-col> </v-col>
<v-col cols="2"></v-col> <v-col cols="2"></v-col>
@ -19,5 +19,4 @@
export default {}; export default {};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -2,7 +2,7 @@
<div> <div>
<v-card class="mt-3"> <v-card class="mt-3">
<v-card-title class="headline"> <v-card-title class="headline">
{{$t('about.about-mealie')}} {{ $t("about.about-mealie") }}
</v-card-title> </v-card-title>
<v-divider></v-divider> <v-divider></v-divider>
<v-card-text> <v-card-text>
@ -22,14 +22,8 @@
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<TheDownloadBtn <TheDownloadBtn :button-text="$t('about.download-recipe-json')" download-url="/api/debug/last-recipe-json" />
:button-text="$t('about.download-recipe-json')" <TheDownloadBtn :button-text="$t('about.download-log')" download-url="/api/debug/log" />
download-url="/api/debug/last-recipe-json"
/>
<TheDownloadBtn
:button-text="$t('about.download-log')"
download-url="/api/debug/log"
/>
</v-card-actions> </v-card-actions>
<v-divider></v-divider> <v-divider></v-divider>
</v-card> </v-card>
@ -55,42 +49,42 @@ export default {
this.prettyInfo = [ this.prettyInfo = [
{ {
name: this.$t('about.version'), name: this.$t("about.version"),
icon: "mdi-information", icon: "mdi-information",
value: debugInfo.version, value: debugInfo.version,
}, },
{ {
name: this.$t('about.application-mode'), name: this.$t("about.application-mode"),
icon: "mdi-dev-to", icon: "mdi-dev-to",
value: debugInfo.production ? this.$t('about.production') : this.$t('about.development'), value: debugInfo.production ? this.$t("about.production") : this.$t("about.development"),
}, },
{ {
name: this.$t('about.demo-status'), name: this.$t("about.demo-status"),
icon: "mdi-test-tube", icon: "mdi-test-tube",
value: debugInfo.demoStatus ? this.$t('about.demo') : this.$t('about.not-demo'), value: debugInfo.demoStatus ? this.$t("about.demo") : this.$t("about.not-demo"),
}, },
{ {
name: this.$t('about.api-port'), name: this.$t("about.api-port"),
icon: "mdi-api", icon: "mdi-api",
value: debugInfo.apiPort, value: debugInfo.apiPort,
}, },
{ {
name: this.$t('about.api-docs'), name: this.$t("about.api-docs"),
icon: "mdi-file-document", icon: "mdi-file-document",
value: debugInfo.apiDocs ? this.$t('general.enabled') : this.$t('general.disabled'), value: debugInfo.apiDocs ? this.$t("general.enabled") : this.$t("general.disabled"),
}, },
{ {
name: this.$t('about.database-type'), name: this.$t("about.database-type"),
icon: "mdi-database", icon: "mdi-database",
value: debugInfo.dbType, value: debugInfo.dbType,
}, },
{ {
name: this.$t('about.sqlite-file'), name: this.$t("about.sqlite-file"),
icon: "mdi-file-cabinet", icon: "mdi-file-cabinet",
value: debugInfo.sqliteFile, value: debugInfo.sqliteFile,
}, },
{ {
name: this.$t('about.default-group'), name: this.$t("about.default-group"),
icon: "mdi-account-group", icon: "mdi-account-group",
value: debugInfo.defaultGroup, value: debugInfo.defaultGroup,
}, },
@ -100,5 +94,4 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -8,15 +8,7 @@
@delete="deleteBackup" @delete="deleteBackup"
/> />
<v-row> <v-row>
<v-col <v-col :cols="12" :sm="6" :md="6" :lg="4" :xl="4" v-for="backup in backups" :key="backup.name">
:cols="12"
:sm="6"
:md="6"
:lg="4"
:xl="4"
v-for="backup in backups"
:key="backup.name"
>
<v-card hover outlined @click="openDialog(backup)"> <v-card hover outlined @click="openDialog(backup)">
<v-card-text> <v-card-text>
<v-row align="center"> <v-row align="center">
@ -63,13 +55,12 @@ export default {
async importBackup(data) { async importBackup(data) {
this.$emit("loading"); this.$emit("loading");
const response = await api.backups.import(data.name, data); const response = await api.backups.import(data.name, data);
if(response) { if (response) {
let importData = response.data; let importData = response.data;
this.$emit("finished", importData); this.$emit("finished", importData);
} else { } else {
this.$emit("finished"); this.$emit("finished");
} }
}, },
async deleteBackup(data) { async deleteBackup(data) {
this.$emit("loading"); this.$emit("loading");
@ -85,5 +76,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -1,10 +1,6 @@
<template> <template>
<div class="text-center"> <div class="text-center">
<v-dialog <v-dialog v-model="dialog" width="500" :fullscreen="$vuetify.breakpoint.xsOnly">
v-model="dialog"
width="500"
:fullscreen="$vuetify.breakpoint.xsOnly"
>
<v-card> <v-card>
<v-toolbar dark color="primary" v-show="$vuetify.breakpoint.xsOnly"> <v-toolbar dark color="primary" v-show="$vuetify.breakpoint.xsOnly">
<v-btn icon dark @click="dialog = false"> <v-btn icon dark @click="dialog = false">
@ -42,12 +38,7 @@
<v-btn color="error" text @click="raiseEvent('delete')"> <v-btn color="error" text @click="raiseEvent('delete')">
{{ $t("general.delete") }} {{ $t("general.delete") }}
</v-btn> </v-btn>
<v-btn <v-btn color="success" outlined @click="raiseEvent('import')" v-show="$vuetify.breakpoint.smAndUp">
color="success"
outlined
@click="raiseEvent('import')"
v-show="$vuetify.breakpoint.smAndUp"
>
{{ $t("general.import") }} {{ $t("general.import") }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@ -56,7 +47,6 @@
</div> </div>
</template> </template>
<script> <script>
import ImportOptions from "./ImportOptions"; import ImportOptions from "./ImportOptions";
import TheDownloadBtn from "@/components/UI/Buttons/TheDownloadBtn.vue"; import TheDownloadBtn from "@/components/UI/Buttons/TheDownloadBtn.vue";
@ -119,5 +109,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -63,5 +63,4 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -2,11 +2,7 @@
<v-card :loading="loading"> <v-card :loading="loading">
<v-card-title> {{ $t("settings.backup.create-heading") }} </v-card-title> <v-card-title> {{ $t("settings.backup.create-heading") }} </v-card-title>
<v-card-text class="mt-n3"> <v-card-text class="mt-n3">
<v-text-field <v-text-field dense :label="$t('settings.backup.backup-tag')" v-model="tag"></v-text-field>
dense
:label="$t('settings.backup.backup-tag')"
v-model="tag"
></v-text-field>
</v-card-text> </v-card-text>
<v-card-actions class="mt-n9 flex-wrap"> <v-card-actions class="mt-n9 flex-wrap">
<v-switch v-model="fullBackup" :label="switchLabel"></v-switch> <v-switch v-model="fullBackup" :label="switchLabel"></v-switch>
@ -101,7 +97,6 @@ export default {
this.$emit("created"); this.$emit("created");
} }
this.loading = false; this.loading = false;
}, },
appendTemplate(templateName) { appendTemplate(templateName) {
if (this.selectedTemplates.includes(templateName)) { if (this.selectedTemplates.includes(templateName)) {
@ -115,5 +110,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -20,19 +20,11 @@
<v-card-title class="mt-n6"> <v-card-title class="mt-n6">
{{ $t("settings.available-backups") }} {{ $t("settings.available-backups") }}
<span> <span>
<TheUploadBtn <TheUploadBtn class="mt-1" url="/api/backups/upload" @uploaded="getAvailableBackups" />
class="mt-1"
url="/api/backups/upload"
@uploaded="getAvailableBackups"
/>
</span> </span>
<v-spacer></v-spacer> <v-spacer></v-spacer>
</v-card-title> </v-card-title>
<AvailableBackupCard <AvailableBackupCard @loading="backupLoading = true" @finished="processFinished" :backups="availableBackups" />
@loading="backupLoading = true"
@finished="processFinished"
:backups="availableBackups"
/>
<ImportSummaryDialog ref="report" :import-data="importData" /> <ImportSummaryDialog ref="report" :import-data="importData" />
</v-card-text> </v-card-text>
@ -80,5 +72,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -17,9 +17,7 @@
<v-list dense> <v-list dense>
<v-card-title class="py-1">{{ group.name }}</v-card-title> <v-card-title class="py-1">{{ group.name }}</v-card-title>
<v-divider></v-divider> <v-divider></v-divider>
<v-subheader>{{ <v-subheader>{{ $t("group.group-id-with-value", { groupID: group.id }) }}</v-subheader>
$t("group.group-id-with-value", { groupID: group.id })
}}</v-subheader>
<v-list-item-group color="primary"> <v-list-item-group color="primary">
<v-list-item v-for="property in groupProps" :key="property.text"> <v-list-item v-for="property in groupProps" :key="property.text">
<v-list-item-icon> <v-list-item-icon>
@ -36,12 +34,7 @@
</v-list> </v-list>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn <v-btn small color="error" @click="confirmDelete" :disabled="ableToDelete">
small
color="error"
@click="confirmDelete"
:disabled="ableToDelete"
>
{{ $t("general.delete") }} {{ $t("general.delete") }}
</v-btn> </v-btn>
<!-- Coming Soon! --> <!-- Coming Soon! -->
@ -113,9 +106,7 @@ export default {
{ {
text: this.$t("user.webhooks-enabled"), text: this.$t("user.webhooks-enabled"),
icon: "mdi-webhook", icon: "mdi-webhook",
value: this.group.webhookEnable value: this.group.webhookEnable ? this.$t("general.yes") : this.$t("general.no"),
? this.$t("general.yes")
: this.$t("general.no"),
}, },
{ {
text: this.$t("user.webhook-time"), text: this.$t("user.webhook-time"),
@ -128,5 +119,4 @@ export default {
}; };
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

@ -16,14 +16,7 @@
</div> </div>
<v-dialog v-model="groupDialog" max-width="400"> <v-dialog v-model="groupDialog" max-width="400">
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn <v-btn class="mx-2" small color="success" dark v-bind="attrs" v-on="on">
class="mx-2"
small
color="success"
dark
v-bind="attrs"
v-on="on"
>
{{ $t("group.create-group") }} {{ $t("group.create-group") }}
</v-btn> </v-btn>
</template> </template>
@ -63,18 +56,8 @@
</v-card-actions> </v-card-actions>
<v-card-text> <v-card-text>
<v-row> <v-row>
<v-col <v-col :sm="6" :md="6" :lg="4" :xl="3" v-for="group in groups" :key="group.id">
:sm="6" <GroupCard :group="group" @update="$store.dispatch('requestAllGroups')" />
:md="6"
:lg="4"
:xl="3"
v-for="group in groups"
:key="group.id"
>
<GroupCard
:group="group"
@update="$store.dispatch('requestAllGroups')"
/>
</v-col> </v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>
@ -114,5 +97,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -48,10 +48,7 @@
:rules="[existsRule]" :rules="[existsRule]"
validate-on-blur validate-on-blur
></v-text-field> ></v-text-field>
<v-checkbox <v-checkbox v-model="editedItem.admin" :label="$t('user.admin')"></v-checkbox>
v-model="editedItem.admin"
:label="$t('user.admin')"
></v-checkbox>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
@ -73,13 +70,7 @@
<v-data-table :headers="headers" :items="links" sort-by="calories"> <v-data-table :headers="headers" :items="links" sort-by="calories">
<template v-slot:item.token="{ item }"> <template v-slot:item.token="{ item }">
{{ `${baseURL}/sign-up/${item.token}` }} {{ `${baseURL}/sign-up/${item.token}` }}
<v-btn <v-btn icon class="mr-1" small color="accent" @click="updateClipboard(`${baseURL}/sign-up/${item.token}`)">
icon
class="mr-1"
small
color="accent"
@click="updateClipboard(`${baseURL}/sign-up/${item.token}`)"
>
<v-icon> <v-icon>
mdi-content-copy mdi-content-copy
</v-icon> </v-icon>
@ -239,5 +230,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -84,17 +84,14 @@
></v-text-field> ></v-text-field>
</v-col> </v-col>
<v-col cols="12" sm="12" md="3"> <v-col cols="12" sm="12" md="3">
<v-switch <v-switch v-model="editedItem.admin" :label="$t('user.admin')"></v-switch>
v-model="editedItem.admin"
:label="$t('user.admin')"
></v-switch>
</v-col> </v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-btn color="info" text @click="resetPassword" v-if="!createMode"> <v-btn color="info" text @click="resetPassword" v-if="!createMode">
{{$t('user.reset-password')}} {{ $t("user.reset-password") }}
</v-btn> </v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="grey" text @click="close"> <v-btn color="grey" text @click="close">
@ -110,12 +107,7 @@
</v-toolbar> </v-toolbar>
<v-divider></v-divider> <v-divider></v-divider>
<v-card-text> <v-card-text>
<v-data-table <v-data-table :headers="headers" :items="users" sort-by="calories" :search="search">
:headers="headers"
:items="users"
sort-by="calories"
:search="search"
>
<template v-slot:item.actions="{ item }"> <template v-slot:item.actions="{ item }">
<v-btn class="mr-1" small color="error" @click="deleteItem(item)"> <v-btn class="mr-1" small color="error" @click="deleteItem(item)">
<v-icon small left> <v-icon small left>
@ -192,9 +184,7 @@ export default {
computed: { computed: {
formTitle() { formTitle() {
return this.createMode return this.createMode ? this.$t("user.new-user") : this.$t("user.edit-user");
? this.$t("user.new-user")
: this.$t("user.edit-user");
}, },
createMode() { createMode() {
return this.editedIndex === -1 ? true : false; return this.editedIndex === -1 ? true : false;
@ -274,21 +264,20 @@ export default {
resetPassword() { resetPassword() {
api.users.resetPassword(this.editedItem.id); api.users.resetPassword(this.editedItem.id);
}, },
async createUser() { async createUser() {
if(await api.users.create(this.editedItem)) { if (await api.users.create(this.editedItem)) {
this.close(); this.close();
} }
}, },
async updateUser() { async updateUser() {
if(await api.users.update(this.editedItem)) { if (await api.users.update(this.editedItem)) {
this.close(); this.close();
} }
} },
}, },
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -1,13 +1,7 @@
<template> <template>
<div> <div>
<v-card flat> <v-card flat>
<v-tabs <v-tabs v-model="tab" background-color="primary" centered dark icons-and-text>
v-model="tab"
background-color="primary"
centered
dark
icons-and-text
>
<v-tabs-slider></v-tabs-slider> <v-tabs-slider></v-tabs-slider>
<v-tab> <v-tab>
@ -58,5 +52,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -14,11 +14,7 @@
v-model="groupSettings.categories" v-model="groupSettings.categories"
:return-object="true" :return-object="true"
:show-add="true" :show-add="true"
:hint=" :hint="$t('meal-plan.only-recipes-with-these-categories-will-be-used-in-meal-plans')"
$t(
'meal-plan.only-recipes-with-these-categories-will-be-used-in-meal-plans'
)
"
/> />
</v-card-text> </v-card-text>
<v-divider> </v-divider> <v-divider> </v-divider>
@ -36,11 +32,7 @@
</p> </p>
<v-row dense class="flex align-center"> <v-row dense class="flex align-center">
<v-switch <v-switch class="mx-2" v-model="groupSettings.webhookEnable" :label="$t('general.enabled')"></v-switch>
class="mx-2"
v-model="groupSettings.webhookEnable"
:label="$t('general.enabled')"
></v-switch>
<TimePickerDialog @save-time="saveTime" class="ma-2" /> <TimePickerDialog @save-time="saveTime" class="ma-2" />
<v-btn class="ma-2" color="info" @click="testWebhooks"> <v-btn class="ma-2" color="info" @click="testWebhooks">
<v-icon left> mdi-webhook </v-icon> <v-icon left> mdi-webhook </v-icon>
@ -48,12 +40,7 @@
</v-btn> </v-btn>
</v-row> </v-row>
<v-row <v-row v-for="(url, index) in groupSettings.webhookUrls" :key="index" align=" center" dense>
v-for="(url, index) in groupSettings.webhookUrls"
:key="index"
align=" center"
dense
>
<v-col cols="1"> <v-col cols="1">
<v-btn icon color="error" @click="removeWebhook(index)"> <v-btn icon color="error" @click="removeWebhook(index)">
<v-icon>mdi-minus</v-icon> <v-icon>mdi-minus</v-icon>
@ -147,5 +134,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -16,12 +16,7 @@
</v-card-title> </v-card-title>
<v-card-text> {{ description }}</v-card-text> <v-card-text> {{ description }}</v-card-text>
<div v-if="available[0]"> <div v-if="available[0]">
<v-card <v-card outlined v-for="migration in available" :key="migration.name" class="ma-2">
outlined
v-for="migration in available"
:key="migration.name"
class="ma-2"
>
<v-card-text> <v-card-text>
<v-row align="center"> <v-row align="center">
<v-col cols="2"> <v-col cols="2">
@ -42,13 +37,7 @@
<v-btn color="error" text @click="deleteMigration(migration.name)"> <v-btn color="error" text @click="deleteMigration(migration.name)">
{{ $t("general.delete") }} {{ $t("general.delete") }}
</v-btn> </v-btn>
<v-btn <v-btn color="accent" text @click="importMigration(migration.name)" :loading="loading" :disabled="loading">
color="accent"
text
@click="importMigration(migration.name)"
:loading="loading"
:disabled="loading"
>
{{ $t("general.import") }} {{ $t("general.import") }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@ -102,5 +91,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -1,5 +1,3 @@
<template> <template>
<div class="text-center"> <div class="text-center">
<v-dialog v-model="dialog" width="70%"> <v-dialog v-model="dialog" width="70%">
@ -105,5 +103,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -8,15 +8,7 @@
<v-card-text> <v-card-text>
<v-row dense> <v-row dense>
<v-col <v-col :cols="12" :sm="6" :md="6" :lg="4" :xl="3" v-for="migration in migrations" :key="migration.title">
:cols="12"
:sm="6"
:md="6"
:lg="4"
:xl="3"
v-for="migration in migrations"
:key="migration.title"
>
<MigrationCard <MigrationCard
:title="migration.title" :title="migration.title"
:folder="migration.urlVariable" :folder="migration.urlVariable"
@ -32,7 +24,6 @@
</div> </div>
</template> </template>
<script> <script>
import MigrationCard from "./MigrationCard"; import MigrationCard from "./MigrationCard";
import { api } from "@/api"; import { api } from "@/api";
@ -88,5 +79,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -4,14 +4,7 @@
<v-card> <v-card>
<v-card-title class="headline"> <v-card-title class="headline">
<span> <span>
<v-progress-circular <v-progress-circular v-if="loading" indeterminate color="primary" large class="mr-2"> </v-progress-circular>
v-if="loading"
indeterminate
color="primary"
large
class="mr-2"
>
</v-progress-circular>
</span> </span>
{{ $t("settings.profile") }} {{ $t("settings.profile") }}
<v-spacer></v-spacer> <v-spacer></v-spacer>
@ -21,16 +14,8 @@
<v-card-text> <v-card-text>
<v-row> <v-row>
<v-col cols="12" md="3" align="center" justify="center"> <v-col cols="12" md="3" align="center" justify="center">
<v-avatar <v-avatar color="accent" size="120" class="white--text headline mr-2">
color="accent" <img :src="userProfileImage" v-if="!hideImage" @error="hideImage = true" />
size="120"
class="white--text headline mr-2"
>
<img
:src="userProfileImage"
v-if="!hideImage"
@error="hideImage = true"
/>
<div v-else> <div v-else>
{{ initials }} {{ initials }}
</div> </div>
@ -113,10 +98,7 @@
v-model="password.newTwo" v-model="password.newTwo"
prepend-icon="mdi-lock" prepend-icon="mdi-lock"
:label="$t('user.confirm-password')" :label="$t('user.confirm-password')"
:rules="[ :rules="[password.newOne === password.newTwo || $t('user.password-must-match')]"
password.newOne === password.newTwo ||
$t('user.password-must-match'),
]"
validate-on-blur validate-on-blur
:type="showPassword ? 'text' : 'password'" :type="showPassword ? 'text' : 'password'"
@click:append="showPassword.newTwo = !showPassword.newTwo" @click:append="showPassword.newTwo = !showPassword.newTwo"
@ -124,11 +106,7 @@
</v-form> </v-form>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-btn <v-btn icon @click="showPassword = !showPassword" :loading="passwordLoading">
icon
@click="showPassword = !showPassword"
:loading="passwordLoading"
>
<v-icon v-if="!showPassword">mdi-eye-off</v-icon> <v-icon v-if="!showPassword">mdi-eye-off</v-icon>
<v-icon v-else> mdi-eye </v-icon> <v-icon v-else> mdi-eye </v-icon>
</v-btn> </v-btn>
@ -202,7 +180,7 @@ export default {
async updateUser() { async updateUser() {
this.loading = true; this.loading = true;
const response = await api.users.update(this.user); const response = await api.users.update(this.user);
if(response) { if (response) {
this.$store.commit("setToken", response.data.access_token); this.$store.commit("setToken", response.data.access_token);
this.refreshProfile(); this.refreshProfile();
this.loading = false; this.loading = false;
@ -227,5 +205,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -14,11 +14,7 @@
</v-app-bar> </v-app-bar>
<v-form ref="newGroup" @submit.prevent="submitForm"> <v-form ref="newGroup" @submit.prevent="submitForm">
<v-card-text> <v-card-text>
<v-text-field <v-text-field autofocus v-model="page.name" :label="$t('settings.page-name')"></v-text-field>
autofocus
v-model="page.name"
:label="$t('settings.page-name')"
></v-text-field>
<CategoryTagSelector <CategoryTagSelector
v-model="page.categories" v-model="page.categories"
ref="categoryFormSelector" ref="categoryFormSelector"
@ -88,7 +84,7 @@ export default {
} else { } else {
response = await api.siteSettings.updatePage(this.page); response = await api.siteSettings.updatePage(this.page);
} }
if (response) { if (response) {
this.pageDialog = false; this.pageDialog = false;
this.page.categories = []; this.page.categories = [];
@ -99,5 +95,4 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -3,22 +3,15 @@
<CreatePageDialog ref="createDialog" @refresh-page="getPages" /> <CreatePageDialog ref="createDialog" @refresh-page="getPages" />
<v-card-text> <v-card-text>
<h2 class="mt-1 mb-1 "> <h2 class="mt-1 mb-1 ">
{{$t('settings.custom-pages')}} {{ $t("settings.custom-pages") }}
<span> <span>
<v-btn color="success" @click="newPage" small class="ml-3"> <v-btn color="success" @click="newPage" small class="ml-3">
{{$t('general.create')}} {{ $t("general.create") }}
</v-btn> </v-btn>
</span> </span>
</h2> </h2>
<draggable class="row mt-1" v-model="customPages"> <draggable class="row mt-1" v-model="customPages">
<v-col <v-col :sm="6" :md="6" :lg="4" :xl="3" v-for="(item, index) in customPages" :key="item + item.id">
:sm="6"
:md="6"
:lg="4"
:xl="3"
v-for="(item, index) in customPages"
:key="item + item.id"
>
<v-card> <v-card>
<v-card-text class="mb-0 pb-0"> <v-card-text class="mb-0 pb-0">
<h3>{{ item.name }}</h3> <h3>{{ item.name }}</h3>
@ -41,11 +34,11 @@
<v-card-actions> <v-card-actions>
<v-btn text small color="error" @click="deletePage(item.id)"> <v-btn text small color="error" @click="deletePage(item.id)">
{{$t('general.delete')}} {{ $t("general.delete") }}
</v-btn> </v-btn>
<v-spacer> </v-spacer> <v-spacer> </v-spacer>
<v-btn small text color="success" @click="editPage(index)"> <v-btn small text color="success" @click="editPage(index)">
{{$t('general.edit')}} {{ $t("general.edit") }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
@ -55,7 +48,7 @@
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="success" @click="savePages"> <v-btn color="success" @click="savePages">
{{$t('general.save')}} {{ $t("general.save") }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
@ -76,8 +69,8 @@ export default {
customPages: [], customPages: [],
newPageData: { newPageData: {
create: true, create: true,
title: this.$t('settings.new-page'), title: this.$t("settings.new-page"),
buttonText: this.$t('general.create'), buttonText: this.$t("general.create"),
data: { data: {
name: "", name: "",
categories: [], categories: [],
@ -86,8 +79,8 @@ export default {
}, },
editPageData: { editPageData: {
create: false, create: false,
title: this.$t('settings.edit-page'), title: this.$t("settings.edit-page"),
buttonText: this.$t('general.update'), buttonText: this.$t("general.update"),
data: {}, data: {},
}, },
}; };
@ -112,7 +105,6 @@ export default {
if (await api.siteSettings.updateAllPages(this.customPages)) { if (await api.siteSettings.updateAllPages(this.customPages)) {
this.getPages(); this.getPages();
} }
}, },
editPage(index) { editPage(index) {
this.editPageData.data = this.customPages[index]; this.editPageData.data = this.customPages[index];
@ -126,5 +118,4 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -4,10 +4,7 @@
<h2 class="mt-1 mb-1">{{ $t("settings.homepage.home-page") }}</h2> <h2 class="mt-1 mb-1">{{ $t("settings.homepage.home-page") }}</h2>
<v-row align="center" justify="center" dense class="mb-n7 pb-n5"> <v-row align="center" justify="center" dense class="mb-n7 pb-n5">
<v-col cols="12" sm="3" md="2"> <v-col cols="12" sm="3" md="2">
<v-switch <v-switch v-model="settings.showRecent" :label="$t('settings.homepage.show-recent')"></v-switch>
v-model="settings.showRecent"
:label="$t('settings.homepage.show-recent')"
></v-switch>
</v-col> </v-col>
<v-col cols="12" sm="5" md="5"> <v-col cols="12" sm="5" md="5">
<v-slider <v-slider
@ -49,10 +46,7 @@
minHeight: `150px`, minHeight: `150px`,
}" }"
> >
<v-list-item <v-list-item v-for="(item, index) in settings.categories" :key="`${item.name}-${index}`">
v-for="(item, index) in settings.categories"
:key="`${item.name}-${index}`"
>
<v-list-item-icon> <v-list-item-icon>
<v-icon>mdi-menu</v-icon> <v-icon>mdi-menu</v-icon>
</v-list-item-icon> </v-list-item-icon>
@ -92,10 +86,7 @@
minHeight: `150px`, minHeight: `150px`,
}" }"
> >
<v-list-item <v-list-item v-for="(item, index) in allCategories" :key="`${item.name}-${index}`">
v-for="(item, index) in allCategories"
:key="`${item.name}-${index}`"
>
<v-list-item-icon> <v-list-item-icon>
<v-icon>mdi-menu</v-icon> <v-icon>mdi-menu</v-icon>
</v-list-item-icon> </v-list-item-icon>
@ -103,9 +94,7 @@
<v-list-item-content> <v-list-item-content>
<v-list-item-title v-text="item.name"></v-list-item-title> <v-list-item-title v-text="item.name"></v-list-item-title>
</v-list-item-content> </v-list-item-content>
<v-list-item-icon <v-list-item-icon @click="deleteCategoryfromDatabase(item.slug)">
@click="deleteCategoryfromDatabase(item.slug)"
>
<v-icon>mdi-delete</v-icon> <v-icon>mdi-delete</v-icon>
</v-list-item-icon> </v-list-item-icon>
</v-list-item> </v-list-item>
@ -214,7 +203,7 @@ export default {
this.settings.language = val; this.settings.language = val;
}, },
deleteCategoryfromDatabase(category) { deleteCategoryfromDatabase(category) {
api.categories.delete(category); api.categories.delete(category);
}, },
async getOptions() { async getOptions() {
this.settings = await api.siteSettings.get(); this.settings = await api.siteSettings.get();
@ -225,11 +214,10 @@ export default {
async saveSettings() { async saveSettings() {
if (await api.siteSettings.update(this.settings)) { if (await api.siteSettings.update(this.settings)) {
this.getOptions(); this.getOptions();
} }
}, },
}, },
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -31,5 +31,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -51,8 +51,7 @@ export default {
dialog: false, dialog: false,
themeName: "", themeName: "",
rules: { rules: {
required: val => required: val => !!val || this.$t("settings.theme.theme-name-is-required"),
!!val || this.$t("settings.theme.theme-name-is-required"),
}, },
}; };
}, },
@ -87,5 +86,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -35,9 +35,7 @@
</v-btn> </v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<!-- <v-btn text color="accent" @click="editTheme">Edit</v-btn> --> <!-- <v-btn text color="accent" @click="editTheme">Edit</v-btn> -->
<v-btn text color="success" @click="saveThemes">{{ <v-btn text color="success" @click="saveThemes">{{ $t("general.apply") }}</v-btn>
$t("general.apply")
}}</v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</div> </div>
@ -87,5 +85,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -15,12 +15,7 @@
</p> </p>
<v-row dense align="center"> <v-row dense align="center">
<v-col cols="6"> <v-col cols="6">
<v-btn-toggle <v-btn-toggle v-model="selectedDarkMode" color="primary " mandatory @change="setStoresDarkMode">
v-model="selectedDarkMode"
color="primary "
mandatory
@change="setStoresDarkMode"
>
<v-btn value="system"> <v-btn value="system">
<v-icon>mdi-desktop-tower-monitor</v-icon> <v-icon>mdi-desktop-tower-monitor</v-icon>
<span class="ml-1" v-show="$vuetify.breakpoint.smAndUp"> <span class="ml-1" v-show="$vuetify.breakpoint.smAndUp">
@ -58,66 +53,33 @@
<v-row dense align-content="center" v-if="selectedTheme.colors"> <v-row dense align-content="center" v-if="selectedTheme.colors">
<v-col> <v-col>
<ColorPickerDialog <ColorPickerDialog :button-text="$t('settings.theme.primary')" v-model="selectedTheme.colors.primary" />
:button-text="$t('settings.theme.primary')"
v-model="selectedTheme.colors.primary"
/>
</v-col> </v-col>
<v-col> <v-col>
<ColorPickerDialog <ColorPickerDialog :button-text="$t('settings.theme.secondary')" v-model="selectedTheme.colors.secondary" />
:button-text="$t('settings.theme.secondary')"
v-model="selectedTheme.colors.secondary"
/>
</v-col> </v-col>
<v-col> <v-col>
<ColorPickerDialog <ColorPickerDialog :button-text="$t('settings.theme.accent')" v-model="selectedTheme.colors.accent" />
:button-text="$t('settings.theme.accent')"
v-model="selectedTheme.colors.accent"
/>
</v-col> </v-col>
<v-col> <v-col>
<ColorPickerDialog <ColorPickerDialog :button-text="$t('settings.theme.success')" v-model="selectedTheme.colors.success" />
:button-text="$t('settings.theme.success')"
v-model="selectedTheme.colors.success"
/>
</v-col> </v-col>
<v-col> <v-col>
<ColorPickerDialog <ColorPickerDialog :button-text="$t('settings.theme.info')" v-model="selectedTheme.colors.info" />
:button-text="$t('settings.theme.info')"
v-model="selectedTheme.colors.info"
/>
</v-col> </v-col>
<v-col> <v-col>
<ColorPickerDialog <ColorPickerDialog :button-text="$t('settings.theme.warning')" v-model="selectedTheme.colors.warning" />
:button-text="$t('settings.theme.warning')"
v-model="selectedTheme.colors.warning"
/>
</v-col> </v-col>
<v-col> <v-col>
<ColorPickerDialog <ColorPickerDialog :button-text="$t('settings.theme.error')" v-model="selectedTheme.colors.error" />
:button-text="$t('settings.theme.error')"
v-model="selectedTheme.colors.error"
/>
</v-col> </v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>
<v-card-text> <v-card-text>
<v-row> <v-row>
<v-col <v-col cols="12" sm="12" md="6" lg="4" xl="3" v-for="theme in availableThemes" :key="theme.name">
cols="12" <ThemeCard :theme="theme" :current="selectedTheme.name == theme.name ? true : false" @delete="getAllThemes" />
sm="12"
md="6"
lg="4"
xl="3"
v-for="theme in availableThemes"
:key="theme.name"
>
<ThemeCard
:theme="theme"
:current="selectedTheme.name == theme.name ? true : false"
@delete="getAllThemes"
/>
</v-col> </v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>
@ -184,15 +146,10 @@ export default {
* This will save the current colors and make the selected theme live. * This will save the current colors and make the selected theme live.
*/ */
saveThemes() { saveThemes() {
api.themes.update( api.themes.update(this.selectedTheme.name, this.selectedTheme.colors);
this.selectedTheme.name,
this.selectedTheme.colors
);
}, },
}, },
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -10,33 +10,16 @@
:top="true" :top="true"
> >
<v-card-text> <v-card-text>
<v-text-field <v-text-field v-model="search" autocomplete="off" :label="$t('general.keyword')"></v-text-field>
v-model="search" <CategoryTagSelector :tag-selector="false" v-model="catsToAssign" :return-object="false" />
autocomplete="off" <CategoryTagSelector :tag-selector="true" v-model="tagsToAssign" :return-object="false" />
:label="$t('general.keyword')"
></v-text-field>
<CategoryTagSelector
:tag-selector="false"
v-model="catsToAssign"
:return-object="false"
/>
<CategoryTagSelector
:tag-selector="true"
v-model="tagsToAssign"
:return-object="false"
/>
</v-card-text> </v-card-text>
<template slot="card-actions"> <template slot="card-actions">
<v-btn text color="grey" @click="closeDialog"> <v-btn text color="grey" @click="closeDialog">
{{ $t("general.cancel") }} {{ $t("general.cancel") }}
</v-btn> </v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn <v-btn color="success" @click="assignAll" :loading="loading" :disabled="results.length < 1">
color="success"
@click="assignAll"
:loading="loading"
:disabled="results.length < 1"
>
{{ $t("settings.toolbox.assign-all") }} {{ $t("settings.toolbox.assign-all") }}
</v-btn> </v-btn>
</template> </template>
@ -122,9 +105,7 @@ export default {
assignAll() { assignAll() {
this.loading = true; this.loading = true;
this.results.forEach(async element => { this.results.forEach(async element => {
element.recipeCategory = element.recipeCategory.concat( element.recipeCategory = element.recipeCategory.concat(this.catsToAssign);
this.catsToAssign
);
element.tags = element.tags.concat(this.tagsToAssign); element.tags = element.tags.concat(this.tagsToAssign);
await api.recipes.patch(element); await api.recipes.patch(element);
}); });
@ -154,9 +135,7 @@ export default {
return []; return [];
} }
return this.allRecipes.filter(x => { return this.allRecipes.filter(x => {
return ( return this.checkForKeywords(x.name) || this.checkForKeywords(x.description);
this.checkForKeywords(x.name) || this.checkForKeywords(x.description)
);
}); });
}, },
checkForKeywords(str) { checkForKeywords(str) {
@ -167,5 +146,4 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -4,11 +4,7 @@
ref="deleteDialog" ref="deleteDialog"
title-icon="mdi-tag" title-icon="mdi-tag"
color="error" color="error"
:title=" :title="$t('general.delete') + ' ' + (isTags ? $t('tag.tags') : $t('recipe.categories'))"
$t('general.delete') +
' ' +
(isTags ? $t('tag.tags') : $t('recipe.categories'))
"
:loading="loading" :loading="loading"
modal-width="400" modal-width="400"
> >
@ -27,12 +23,7 @@
{{ $t("general.cancel") }} {{ $t("general.cancel") }}
</v-btn> </v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn <v-btn color="error" @click="deleteUnused" :loading="loading" :disabled="deleteList.length < 1">
color="error"
@click="deleteUnused"
:loading="loading"
:disabled="deleteList.length < 1"
>
{{ $t("general.delete") }} {{ $t("general.delete") }}
</v-btn> </v-btn>
</template> </template>
@ -95,5 +86,4 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -43,24 +43,14 @@
<BulkAssign isTags="isTags" class="mr-1 mb-1" /> <BulkAssign isTags="isTags" class="mr-1 mb-1" />
<v-btn <v-btn @click="titleCaseAll" small color="success" class="mr-1 mb-1" :loading="loadingTitleCase">
@click="titleCaseAll"
small
color="success"
class="mr-1 mb-1"
:loading="loadingTitleCase"
>
{{ $t("settings.toolbox.title-case-all") }} {{ $t("settings.toolbox.title-case-all") }}
</v-btn> </v-btn>
<RemoveUnused :isTags="isTags" class="mb-1" /> <RemoveUnused :isTags="isTags" class="mb-1" />
<v-spacer v-if="!isMobile"> </v-spacer> <v-spacer v-if="!isMobile"> </v-spacer>
<fuse-search-bar <fuse-search-bar :raw-data="allItems" @results="filterItems" :search="searchString">
:raw-data="allItems"
@results="filterItems"
:search="searchString"
>
<v-text-field <v-text-field
v-model="searchString" v-model="searchString"
clearable clearable
@ -79,24 +69,16 @@
<v-card-text> <v-card-text>
<v-row> <v-row>
<v-col <v-col cols="12" :sm="12" :md="6" :lg="4" :xl="3" v-for="item in results" :key="item.id">
cols="12"
:sm="12"
:md="6"
:lg="4"
:xl="3"
v-for="item in results"
:key="item.id"
>
<v-card> <v-card>
<v-card-actions> <v-card-actions>
<v-card-title class="py-1">{{ item.name }}</v-card-title> <v-card-title class="py-1">{{ item.name }}</v-card-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn small text color="info" @click="openEditDialog(item)"> <v-btn small text color="info" @click="openEditDialog(item)">
{{$t('general.edit')}} {{ $t("general.edit") }}
</v-btn> </v-btn>
<v-btn small text color="error" @click="deleteItem(item.slug)"> <v-btn small text color="error" @click="deleteItem(item.slug)">
{{$t('general.delete')}} {{ $t("general.delete") }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
@ -149,9 +131,7 @@ export default {
return this.$vuetify.breakpoint.name === "xs"; return this.$vuetify.breakpoint.name === "xs";
}, },
allItems() { allItems() {
return this.isTags return this.isTags ? this.$store.getters.getAllTags : this.$store.getters.getAllCategories;
? this.$store.getters.getAllTags
: this.$store.getters.getAllCategories;
}, },
results() { results() {
if (this.searchString != null && this.searchString.length >= 1) { if (this.searchString != null && this.searchString.length >= 1) {
@ -176,7 +156,7 @@ export default {
} }
this.renameTarget = { this.renameTarget = {
title:this.$t('general.rename-object', [item.name]), title: this.$t("general.rename-object", [item.name]),
name: item.name, name: item.name,
slug: item.slug, slug: item.slug,
newName: "", newName: "",
@ -240,4 +220,4 @@ export default {
height: auto !important; height: auto !important;
flex-wrap: wrap; flex-wrap: wrap;
} }
</style> </style>

View File

@ -1,13 +1,7 @@
<template> <template>
<div> <div>
<v-card flat> <v-card flat>
<v-tabs <v-tabs v-model="tab" background-color="primary" centered dark icons-and-text>
v-model="tab"
background-color="primary"
centered
dark
icons-and-text
>
<v-tabs-slider></v-tabs-slider> <v-tabs-slider></v-tabs-slider>
<v-tab> <v-tab>
@ -43,5 +37,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -12,5 +12,4 @@
export default {}; export default {};
</script> </script>
<style> <style></style>
</style>

View File

@ -16,5 +16,4 @@ export default {
}; };
</script> </script>
<style> <style></style>
</style>

View File

@ -1,6 +1,5 @@
<template> <template>
<v-container> <v-container>
<CardSection <CardSection
v-if="siteSettings.showRecent" v-if="siteSettings.showRecent"
:title="$t('page.recent')" :title="$t('page.recent')"
@ -38,8 +37,8 @@ export default {
return this.$store.getters.getSiteSettings; return this.$store.getters.getSiteSettings;
}, },
recentRecipes() { recentRecipes() {
console.log("Recent Recipes");
return this.$store.getters.getRecentRecipes; return this.$store.getters.getRecentRecipes;
}, },
}, },
async mounted() { async mounted() {
@ -62,18 +61,13 @@ export default {
this.$store.dispatch("requestRecentRecipes"); this.$store.dispatch("requestRecentRecipes");
}, },
sortAZ(index) { sortAZ(index) {
this.recipeByCategory[index].recipes.sort((a, b) => this.recipeByCategory[index].recipes.sort((a, b) => (a.name > b.name ? 1 : -1));
a.name > b.name ? 1 : -1
);
}, },
sortRecent(index) { sortRecent(index) {
this.recipeByCategory[index].recipes.sort((a, b) => this.recipeByCategory[index].recipes.sort((a, b) => (a.dateAdded > b.dateAdded ? -1 : 1));
a.dateAdded > b.dateAdded ? -1 : 1
);
}, },
}, },
}; };
</script> </script>
<style> <style></style>
</style>

Some files were not shown because too many files have changed in this diff Show More