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
#----------------------------------------------
# - name: Load cached venv
# id: cached-poetry-dependencies
# uses: actions/cache@v2
# with:
# path: .venv
# key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v2
with:
path: .venv
key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
#----------------------------------------------
# install dependencies if cache does not exist
#----------------------------------------------

View file

@ -27,6 +27,12 @@
- Title case all Categories or Tags with 1 click
- Create/Rename/Delete Operations for Tags/Categories
- 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
- 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 = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
presets: ["@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",
"@vue/cli-plugin-babel": "^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",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
@ -65,6 +65,7 @@
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": false
"singleQuote": false,
"printWidth": 120
}
}

View file

@ -4,9 +4,7 @@
<TheAppBar />
<v-main>
<v-banner v-if="demo" sticky
><div class="text-center">
<b> This is a Demo</b> | Username: changeme@email.com | Password: demo
</div></v-banner
><div class="text-center"><b> This is a Demo</b> | Username: changeme@email.com | Password: demo</div></v-banner
>
<router-view></router-view>
</v-main>
@ -57,9 +55,7 @@ export default {
*/
darkModeSystemCheck() {
if (this.$store.getters.getDarkMode === "system")
Vuetify.framework.theme.dark = window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
Vuetify.framework.theme.dark = window.matchMedia("(prefers-color-scheme: dark)").matches;
},
/**
* 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 utils from "@/utils";
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${store.getters.getToken}`;
axios.defaults.headers.common["Authorization"] = `Bearer ${store.getters.getToken}`;
function handleError(error, getText) {
if(getText) {
if (getText) {
utils.notify.error(getText(error.response));
}
return false;
}
function handleResponse(response, getText) {
if(response && getText) {
if (response && getText) {
const successText = getText(response);
utils.notify.success(successText);
}
@ -31,26 +29,36 @@ function defaultSuccessText(response) {
const apiReq = {
post: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
const response = await axios.post(url, data).catch(function(error) { handleError(error, getErrorText) });
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) });
const response = await axios.post(url, data).catch(function(error) {
handleError(error, getErrorText);
});
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) {
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);
},
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 ) {
const response = await axios.delete(url, data).catch( function(error) { handleError(error, getErrorText) } );
delete: async function(url, data, getErrorText = defaultErrorText, getSuccessText = defaultSuccessText) {
const response = await axios.delete(url, data).catch(function(error) {
handleError(error, getErrorText);
});
return handleResponse(response, getSuccessText);
},

View file

@ -1,7 +1,7 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import { store } from "@/store";
import i18n from '@/i18n.js';
import i18n from "@/i18n.js";
const backupBase = baseURL + "backups/";
@ -14,8 +14,6 @@ export const backupURLs = {
downloadBackup: fileName => `${backupBase}${fileName}/download`,
};
export const backupAPI = {
/**
* Request all backups available on the server
@ -44,8 +42,8 @@ export const backupAPI = {
return apiReq.delete(
backupURLs.deleteBackup(fileName),
null,
function() { return i18n.t('settings.backup.unable-to-delete-backup'); },
function() { return i18n.t('settings.backup.backup-deleted'); }
() => i18n.t("settings.backup.unable-to-delete-backup"),
() => i18n.t("settings.backup.backup-deleted")
);
},
/**
@ -55,10 +53,12 @@ export const backupAPI = {
*/
async create(options) {
return apiReq.post(
backupURLs.createBackup,
backupURLs.createBackup,
options,
function() { return 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}); }
() => i18n.t("settings.backup.error-creating-backup-see-log-file"),
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 { apiReq } from "./api-utils";
import { store } from "@/store";
import i18n from '@/i18n.js';
import i18n from "@/i18n.js";
const prefix = baseURL + "categories";
@ -24,12 +24,12 @@ export const categoryAPI = {
},
async create(name) {
const response = await apiReq.post(
categoryURLs.getAll,
categoryURLs.getAll,
{ name: name },
function() { return i18n.t('category.category-creation-failed'); },
function() { return i18n.t('category.category-created'); }
() => i18n.t("category.category-creation-failed"),
() => i18n.t("category.category-created")
);
if(response) {
if (response) {
store.dispatch("requestCategories");
return response.data;
}
@ -40,10 +40,10 @@ export const categoryAPI = {
},
async update(name, newName, overrideRequest = false) {
const response = await apiReq.put(
categoryURLs.updateCategory(name),
categoryURLs.updateCategory(name),
{ name: newName },
function() { return i18n.t('category.category-update-failed'); },
function() { return i18n.t('category.category-updated'); }
() => i18n.t("category.category-update-failed"),
() => i18n.t("category.category-updated")
);
if (response && !overrideRequest) {
store.dispatch("requestCategories");
@ -54,8 +54,8 @@ export const categoryAPI = {
const response = await apiReq.delete(
categoryURLs.deleteCategory(category),
null,
function() { return i18n.t('category.category-deletion-failed'); },
function() { return i18n.t('category.category-deleted'); }
() => i18n.t("category.category-deletion-failed"),
() => i18n.t("category.category-deleted")
);
if (response && !overrideRequest) {
store.dispatch("requestCategories");
@ -85,12 +85,12 @@ export const tagAPI = {
},
async create(name) {
const response = await apiReq.post(
tagURLs.getAll,
tagURLs.getAll,
{ name: name },
function() { return i18n.t('tag.tag-creation-failed'); },
function() { return i18n.t('tag.tag-created'); }
() => i18n.t("tag.tag-creation-failed"),
() => i18n.t("tag.tag-created")
);
if(response) {
if (response) {
store.dispatch("requestTags");
return response.data;
}
@ -101,13 +101,13 @@ export const tagAPI = {
},
async update(name, newName, overrideRequest = false) {
const response = await apiReq.put(
tagURLs.updateTag(name),
tagURLs.updateTag(name),
{ name: newName },
function() { return i18n.t('tag.tag-update-failed'); },
function() { return i18n.t('tag.tag-updated'); }
() => i18n.t("tag.tag-update-failed"),
() => i18n.t("tag.tag-updated")
);
if(response) {
if (response) {
if (!overrideRequest) {
store.dispatch("requestTags");
}
@ -118,10 +118,10 @@ export const tagAPI = {
const response = await apiReq.delete(
tagURLs.deleteTag(tag),
null,
function() { return i18n.t('tag.tag-deletion-failed'); },
function() { return i18n.t('tag.tag-deleted'); }
() => i18n.t("tag.tag-deletion-failed"),
() => i18n.t("tag.tag-deleted")
);
if(response) {
if (response) {
if (!overrideRequest) {
store.dispatch("requestTags");
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import i18n from '@/i18n.js';
import i18n from "@/i18n.js";
const signUpPrefix = baseURL + "users/sign-ups";
@ -18,24 +18,27 @@ export const signupAPI = {
},
async createToken(data) {
let response = await apiReq.post(
signUpURLs.createToken,
signUpURLs.createToken,
data,
function() { return i18n.t('signup.sign-up-link-creation-failed'); },
function() { return i18n.t('signup.sign-up-link-created'); }
() => i18n.t("signup.sign-up-link-creation-failed"),
() => i18n.t("signup.sign-up-link-created")
);
return response.data;
},
async deleteToken(token) {
return await apiReq.delete(signUpURLs.deleteToken(token),
null,
function() { return i18n.t('signup.sign-up-token-deletion-failed'); },
function() { return i18n.t('signup.sign-up-token-deleted'); }
return await apiReq.delete(
signUpURLs.deleteToken(token),
null,
() => i18n.t("signup.sign-up-token-deletion-failed"),
() => i18n.t("signup.sign-up-token-deleted")
);
},
async createUser(token, data) {
return apiReq.post(signUpURLs.createUser(token), data,
function() { return i18n.t('user.you-are-not-allowed-to-create-a-user'); },
function() { return i18n.t('user.user-created'); }
return apiReq.post(
signUpURLs.createUser(token),
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 { apiReq } from "./api-utils";
import { store } from "@/store";
import i18n from '@/i18n.js';
import i18n from "@/i18n.js";
const settingsBase = baseURL + "site-settings";
@ -21,12 +21,12 @@ export const siteSettingsAPI = {
async update(body) {
const response = await apiReq.put(
settingsURLs.updateSiteSettings,
settingsURLs.updateSiteSettings,
body,
function() { return i18n.t('settings.settings-update-failed'); },
function() { return i18n.t('settings.settings-updated'); }
() => i18n.t("settings.settings-update-failed"),
() => i18n.t("settings.settings-updated")
);
if(response) {
if (response) {
store.dispatch("requestSiteSettings");
}
return response;
@ -44,10 +44,10 @@ export const siteSettingsAPI = {
createPage(body) {
return apiReq.post(
settingsURLs.customPages,
settingsURLs.customPages,
body,
function() { return i18n.t('page.page-creation-failed'); },
function() { return i18n.t('page.new-page-created'); }
() => i18n.t("page.page-creation-failed"),
() => i18n.t("page.new-page-created")
);
},
@ -55,25 +55,26 @@ export const siteSettingsAPI = {
return await apiReq.delete(
settingsURLs.customPage(id),
null,
function() { return i18n.t('page.page-deletion-failed'); },
function() { return i18n.t('page.page-deleted'); });
() => i18n.t("page.page-deletion-failed"),
() => i18n.t("page.page-deleted")
);
},
updatePage(body) {
return apiReq.put(
settingsURLs.customPage(body.id),
body,
function() { return i18n.t('page.page-update-failed'); },
function() { return i18n.t('page.page-updated'); }
() => i18n.t("page.page-update-failed"),
() => i18n.t("page.page-updated")
);
},
async updateAllPages(allPages) {
let response = await apiReq.put(
settingsURLs.customPages,
settingsURLs.customPages,
allPages,
function() { return i18n.t('page.pages-update-failed'); },
function() { return i18n.t('page.pages-updated'); }
() => i18n.t("page.pages-update-failed"),
() => i18n.t("page.pages-updated")
);
return response;
},

View file

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

View file

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

View file

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

View file

@ -31,11 +31,7 @@
</v-chip>
</template>
<template v-slot:append-outer="">
<NewCategoryTagDialog
v-if="showAdd"
:tag-dialog="tagSelector"
@created-item="pushToItem"
/>
<NewCategoryTagDialog v-if="showAdd" :tag-dialog="tagSelector" @created-item="pushToItem" />
</template>
</v-autocomplete>
</template>
@ -90,7 +86,7 @@ export default {
computed: {
inputLabel() {
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() {
let ItemObjects = [];
@ -125,5 +121,4 @@ export default {
};
</script>
<style lang="scss" scoped>
</style>
<style lang="scss" scoped></style>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,28 +3,21 @@
<v-dialog v-model="dialog" width="650">
<v-card>
<v-card-title class="headline">
{{$t('meal-plan.shopping-list')}}
{{ $t("meal-plan.shopping-list") }}
<v-spacer></v-spacer>
<v-btn text color="accent" @click="group = !group">
{{$t('meal-plan.group')}}
{{ $t("meal-plan.group") }}
</v-btn>
</v-card-title>
<v-divider></v-divider>
<v-card-text v-if="group == false">
<v-list
dense
v-for="(recipe, index) in ingredients"
:key="`${index}-recipe`"
>
<v-list dense v-for="(recipe, index) in ingredients" :key="`${index}-recipe`">
<v-subheader>{{ recipe.name }} </v-subheader>
<v-divider></v-divider>
<v-list-item-group color="primary">
<v-list-item
v-for="(item, i) in recipe.recipe_ingredient"
:key="i"
>
<v-list-item v-for="(item, i) in recipe.recipe_ingredient" :key="i">
<v-list-item-content>
<v-list-item-title v-text="item"></v-list-item-title>
</v-list-item-content>
@ -104,8 +97,4 @@ export default {
};
</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>
<v-col></v-col>
<div v-if="open">
<v-btn
class="mr-2"
fab
dark
small
color="error"
@click="deleteRecipeConfrim"
>
<v-btn class="mr-2" fab dark small color="error" @click="deleteRecipeConfrim">
<v-icon>mdi-delete</v-icon>
</v-btn>
@ -101,5 +94,4 @@ export default {
};
</script>
<style>
</style>
<style></style>

View file

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

View file

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

View file

@ -2,24 +2,17 @@
<div class="text-center">
<v-dialog v-model="dialog" width="600">
<template v-slot:activator="{ on, attrs }">
<v-btn
color="secondary lighten-2"
dark
v-bind="attrs"
v-on="on"
@click="inputText = ''"
>
{{$t('new-recipe.bulk-add')}}
<v-btn color="secondary lighten-2" dark v-bind="attrs" v-on="on" @click="inputText = ''">
{{ $t("new-recipe.bulk-add") }}
</v-btn>
</template>
<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>
<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>
<v-textarea v-model="inputText"> </v-textarea>
</v-card-text>
@ -28,7 +21,7 @@
<v-card-actions>
<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>
</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-text :key="formKey">
<v-row
align="center"
v-for="(value, key, index) in extras"
:key="index"
>
<v-row align="center" v-for="(value, key, index) in extras" :key="index">
<v-col cols="12" sm="1">
<v-btn
fab
text
x-small
color="white"
elevation="0"
@click="removeExtra(key)"
>
<v-btn fab text x-small color="white" elevation="0" @click="removeExtra(key)">
<v-icon color="error">mdi-delete</v-icon>
</v-btn>
</v-col>
<v-col cols="12" md="3" sm="6">
<v-text-field
:label="$t('recipe.object-key')"
:value="key"
@input="updateKey(index)"
>
</v-text-field>
<v-text-field :label="$t('recipe.object-key')" :value="key" @input="updateKey(index)"> </v-text-field>
</v-col>
<v-col cols="12" md="8" sm="6">
<v-text-field :label="$t('recipe.object-value')" v-model="extras[key]">
</v-text-field>
<v-text-field :label="$t('recipe.object-value')" v-model="extras[key]"> </v-text-field>
</v-col>
</v-row>
</v-card-text>
@ -74,9 +57,8 @@ export default {
dialog: false,
formKey: 1,
rules: {
required: (v) => !!v || this.$i18n.t("recipe.key-name-required"),
whiteSpace: (v) =>
!v || v.split(" ").length <= 1 || this.$i18n.t("recipe.no-white-space-allowed"),
required: v => !!v || this.$i18n.t("recipe.key-name-required"),
whiteSpace: v => !v || v.split(" ").length <= 1 || this.$i18n.t("recipe.no-white-space-allowed"),
},
};
},
@ -100,5 +82,4 @@ export default {
};
</script>
<style>
</style>
<style></style>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,7 @@
<template>
<v-form ref="file">
<input ref="uploader" class="d-none" type="file" @change="onFileChanged" />
<v-btn
:loading="isSelecting"
@click="onButtonClick"
color="accent"
:text="textBtn"
>
<v-btn :loading="isSelecting" @click="onButtonClick" color="accent" :text="textBtn">
<v-icon left> {{ icon }}</v-icon>
{{ text ? text : defaultText }}
</v-btn>
@ -55,7 +50,7 @@ export default {
let formData = new FormData();
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.isSelecting = false;
@ -81,5 +76,4 @@ export default {
};
</script>
<style>
</style>
<style></style>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -22,7 +22,7 @@ const vueApp = new Vue({
}).$mount("#app");
// Truncate
let truncate = function(text, length, clamp) {
const truncate = function(text, length, clamp) {
clamp = clamp || "...";
let node = document.createElement("div");
node.innerHTML = text;
@ -30,7 +30,7 @@ let truncate = function(text, length, clamp) {
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());
};

View file

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

View file

@ -5,9 +5,9 @@
<v-col>
<v-card height="">
<v-card-text>
<h1>{{$t('404.page-not-found')}}</h1>
<h1>{{ $t("404.page-not-found") }}</h1>
</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-col>
<v-col cols="2"></v-col>
@ -19,5 +19,4 @@
export default {};
</script>
<style lang="scss" scoped>
</style>
<style lang="scss" scoped></style>

View file

@ -2,7 +2,7 @@
<div>
<v-card class="mt-3">
<v-card-title class="headline">
{{$t('about.about-mealie')}}
{{ $t("about.about-mealie") }}
</v-card-title>
<v-divider></v-divider>
<v-card-text>
@ -22,14 +22,8 @@
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<TheDownloadBtn
:button-text="$t('about.download-recipe-json')"
download-url="/api/debug/last-recipe-json"
/>
<TheDownloadBtn
:button-text="$t('about.download-log')"
download-url="/api/debug/log"
/>
<TheDownloadBtn :button-text="$t('about.download-recipe-json')" download-url="/api/debug/last-recipe-json" />
<TheDownloadBtn :button-text="$t('about.download-log')" download-url="/api/debug/log" />
</v-card-actions>
<v-divider></v-divider>
</v-card>
@ -55,42 +49,42 @@ export default {
this.prettyInfo = [
{
name: this.$t('about.version'),
name: this.$t("about.version"),
icon: "mdi-information",
value: debugInfo.version,
},
{
name: this.$t('about.application-mode'),
name: this.$t("about.application-mode"),
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",
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",
value: debugInfo.apiPort,
},
{
name: this.$t('about.api-docs'),
name: this.$t("about.api-docs"),
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",
value: debugInfo.dbType,
},
{
name: this.$t('about.sqlite-file'),
name: this.$t("about.sqlite-file"),
icon: "mdi-file-cabinet",
value: debugInfo.sqliteFile,
},
{
name: this.$t('about.default-group'),
name: this.$t("about.default-group"),
icon: "mdi-account-group",
value: debugInfo.defaultGroup,
},
@ -100,5 +94,4 @@ export default {
};
</script>
<style lang="scss" scoped>
</style>
<style lang="scss" scoped></style>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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