Compare commits

..

2 Commits

Author SHA1 Message Date
Kuchenpirat
6e16a16cd8 Merge branch 'mealie-next' into reset-scroll-position 2025-07-12 01:32:32 +02:00
Kuchenpirat
78a0f74f33 reset scroll position 2025-07-11 23:29:09 +00:00
16 changed files with 1722 additions and 1476 deletions

View File

@@ -17,7 +17,7 @@ jobs:
name: Build Package
uses: ./.github/workflows/build-package.yml
with:
tag: ${{ github.event.release.tag_name }}
tag: release
publish:
permissions:

View File

@@ -123,6 +123,7 @@
:image="recipe.image!"
:tags="recipe.tags!"
:recipe-id="recipe.id!"
@click="handleRecipeNavigation"
/>
</v-col>
</v-row>
@@ -147,6 +148,7 @@
:image="recipe.image!"
:tags="recipe.tags!"
:recipe-id="recipe.id!"
@selected="handleRecipeNavigation"
/>
</v-col>
</v-row>
@@ -171,6 +173,7 @@ import { useLazyRecipes } from "~/composables/recipes";
import type { Recipe } from "~/lib/api/types/recipe";
import { useUserSortPreferences } from "~/composables/use-users/preferences";
import type { RecipeSearchQuery } from "~/lib/api/user/recipes/recipe";
import { useRecipeListState } from "~/composables/recipe-page/use-recipe-list-state";
const REPLACE_RECIPES_EVENT = "replaceRecipes";
const APPEND_RECIPES_EVENT = "appendRecipes";
@@ -241,9 +244,11 @@ export default defineNuxtComponent({
const route = useRoute();
const groupSlug = computed(() => route.params.groupSlug as string || $auth.user.value?.groupSlug || "");
const page = ref(1);
const recipeListState = useRecipeListState(props.query);
const page = ref(recipeListState.state.page || 1);
const perPage = 32;
const hasMore = ref(true);
const hasMore = ref(recipeListState.state.hasMore);
const ready = ref(false);
const loading = ref(false);
@@ -282,8 +287,33 @@ export default defineNuxtComponent({
);
}
// Save scroll position
const throttledScrollSave = useThrottleFn(() => {
recipeListState.saveScrollPosition();
}, 1000);
onMounted(async () => {
await initRecipes();
window.addEventListener("scroll", throttledScrollSave);
// cached state with scroll position
if (recipeListState.hasValidState() && recipeListState.isQueryMatch(props.query)) {
// Restore from cached state
page.value = recipeListState.state.page;
hasMore.value = recipeListState.state.hasMore;
ready.value = recipeListState.state.ready;
// Emit cached recipes
context.emit(REPLACE_RECIPES_EVENT, recipeListState.state.recipes);
// Restore scroll position after recipes are rendered
nextTick(() => {
recipeListState.restoreScrollPosition();
});
}
else {
// Initialize fresh recipes
await initRecipes();
}
ready.value = true;
});
@@ -294,6 +324,10 @@ export default defineNuxtComponent({
const newValueString = JSON.stringify(newValue);
if (lastQuery !== newValueString) {
lastQuery = newValueString;
// Save scroll position before query change
recipeListState.saveScrollPosition();
ready.value = false;
await initRecipes();
ready.value = true;
@@ -315,6 +349,14 @@ export default defineNuxtComponent({
// since we doubled the first call, we also need to advance the page
page.value = page.value + 1;
// Save state after fetching recipes
recipeListState.saveState({
recipes: newRecipes,
page: page.value,
hasMore: hasMore.value,
ready: true,
});
context.emit(REPLACE_RECIPES_EVENT, newRecipes);
}
@@ -331,6 +373,14 @@ export default defineNuxtComponent({
hasMore.value = false;
}
if (newRecipes.length) {
// Update cached state with new recipes
const allRecipes = [...(recipeListState.state.recipes || []), ...newRecipes] as Recipe[];
recipeListState.saveState({
recipes: allRecipes,
page: page.value,
hasMore: hasMore.value,
});
context.emit(APPEND_RECIPES_EVENT, newRecipes);
}
@@ -408,6 +458,15 @@ export default defineNuxtComponent({
// fetch new recipes
const newRecipes = await fetchRecipes();
// Update cached state
recipeListState.saveState({
recipes: newRecipes,
page: page.value,
hasMore: hasMore.value,
ready: true,
});
context.emit(REPLACE_RECIPES_EVENT, newRecipes);
state.sortLoading = false;
@@ -427,6 +486,17 @@ export default defineNuxtComponent({
preferences.value.useMobileCards = !preferences.value.useMobileCards;
}
// Save scroll position when component is unmounted or when navigating away
onBeforeUnmount(() => {
recipeListState.saveScrollPosition();
window.removeEventListener("scroll", throttledScrollSave);
});
// Save scroll position when navigating to recipe pages
function handleRecipeNavigation() {
recipeListState.saveScrollPosition();
}
return {
...toRefs(state),
displayTitleIcon,
@@ -439,6 +509,7 @@ export default defineNuxtComponent({
sortRecipes,
toggleMobileCards,
useMobileCards,
handleRecipeNavigation,
};
},
});

View File

@@ -42,7 +42,7 @@
color="info"
variant="elevated"
:items="bulkActions"
v-on="bulkActionListener"
v-bind="bulkActionListener"
/>
<slot name="button-row" />
</v-card-actions>
@@ -55,7 +55,7 @@
</div>
<v-data-table
v-model="selected"
return-object
item-key="id"
:headers="activeHeaders"
:show-select="bulkActions.length > 0"
:sort-by="sortBy"

View File

@@ -0,0 +1,94 @@
import type { Recipe } from "~/lib/api/types/recipe";
import type { RecipeSearchQuery } from "~/lib/api/user/recipes/recipe";
interface RecipeListState {
recipes: Recipe[];
page: number;
hasMore: boolean;
scrollPosition: number;
query: RecipeSearchQuery | null;
ready: boolean;
}
const recipeListStates = new Map<string, RecipeListState>();
function generateStateKey(query: RecipeSearchQuery | null): string {
if (!query) return "default";
const keyParts = [
query.search || "",
query.orderBy || "",
query.orderDirection || "",
query.queryFilter || "",
JSON.stringify(query.categories || []),
JSON.stringify(query.tags || []),
JSON.stringify(query.tools || []),
JSON.stringify(query.foods || []),
JSON.stringify(query.households || []),
];
return keyParts.join("|");
}
export function useRecipeListState(query: RecipeSearchQuery | null) {
const stateKey = generateStateKey(query);
// Initialize state if it doesn't exist
if (!recipeListStates.has(stateKey)) {
recipeListStates.set(stateKey, {
recipes: [],
page: 1,
hasMore: true,
scrollPosition: 0,
query,
ready: false,
});
}
const state = recipeListStates.get(stateKey)!;
function saveState(newState: Partial<RecipeListState>) {
Object.assign(state, newState);
}
function saveScrollPosition() {
state.scrollPosition = window.scrollY || document.documentElement.scrollTop || 0;
}
function restoreScrollPosition() {
if (state.scrollPosition > 0) {
// Use nextTick to ensure DOM is updated before scrolling
nextTick(() => {
window.scrollTo(0, state.scrollPosition);
});
}
}
function clearState() {
recipeListStates.delete(stateKey);
}
function hasValidState(): boolean {
return state.recipes.length > 0 && state.ready;
}
function isQueryMatch(newQuery: RecipeSearchQuery | null): boolean {
const newKey = generateStateKey(newQuery);
return newKey === stateKey;
}
return {
state: readonly(state),
saveState,
saveScrollPosition,
restoreScrollPosition,
clearState,
hasValidState,
isQueryMatch,
};
}
// Clean up old states when navigating away from recipe sections
export function cleanupRecipeListStates() {
recipeListStates.clear();
}

View File

@@ -45,11 +45,28 @@ export const useGroupSelf = function () {
export const useGroups = function () {
const api = useUserApi();
const loading = ref(false);
const groups = ref<GroupSummary[] | null>(null);
async function getAllGroups() {
function getAllGroups() {
loading.value = true;
const { data } = await api.groups.getAll(1, -1, { orderBy: "name", orderDirection: "asc" });
const asyncKey = String(Date.now());
const { data: groups } = useAsyncData(asyncKey, async () => {
const { data } = await api.groups.getAll(1, -1, { orderBy: "name", orderDirection: "asc" }); ;
if (data) {
return data.items;
}
else {
return null;
}
});
loading.value = false;
return groups;
}
async function refreshAllGroups() {
loading.value = true;
const { data } = await api.groups.getAll(1, -1, { orderBy: "name", orderDirection: "asc" }); ;
if (data) {
groups.value = data.items;
@@ -61,15 +78,11 @@ export const useGroups = function () {
loading.value = false;
}
async function refreshAllGroups() {
await getAllGroups();
}
async function deleteGroup(id: string | number) {
loading.value = true;
const { data } = await api.groups.deleteOne(id);
loading.value = false;
await refreshAllGroups();
refreshAllGroups();
return data;
}
@@ -80,13 +93,9 @@ export const useGroups = function () {
if (data && groups.value) {
groups.value.push(data);
}
loading.value = false;
}
// Initialize data on first call
if (!groups.value) {
getAllGroups();
}
const groups = getAllGroups();
return { groups, getAllGroups, refreshAllGroups, deleteGroup, createGroup };
};

View File

@@ -48,11 +48,28 @@ export const useHouseholdSelf = function () {
export const useAdminHouseholds = function () {
const api = useAdminApi();
const loading = ref(false);
const households = ref<HouseholdInDB[] | null>(null);
async function getAllHouseholds() {
function getAllHouseholds() {
loading.value = true;
const { data } = await api.households.getAll(1, -1, { orderBy: "name, group.name", orderDirection: "asc" });
const asyncKey = String(Date.now());
const { data: households } = useAsyncData(asyncKey, async () => {
const { data } = await api.households.getAll(1, -1, { orderBy: "name, group.name", orderDirection: "asc" });
if (data) {
return data.items;
}
else {
return null;
}
});
loading.value = false;
return households;
}
async function refreshAllHouseholds() {
loading.value = true;
const { data } = await api.households.getAll(1, -1, { orderBy: "name, group.name", orderDirection: "asc" }); ;
if (data) {
households.value = data.items;
@@ -64,15 +81,11 @@ export const useAdminHouseholds = function () {
loading.value = false;
}
async function refreshAllHouseholds() {
await getAllHouseholds();
}
async function deleteHousehold(id: string | number) {
loading.value = true;
const { data } = await api.households.deleteOne(id);
loading.value = false;
await refreshAllHouseholds();
refreshAllHouseholds();
return data;
}
@@ -83,9 +96,9 @@ export const useAdminHouseholds = function () {
if (data && households.value) {
households.value.push(data);
}
loading.value = false;
}
const households = getAllHouseholds();
function useHouseholdsInGroup(groupIdRef: Ref<string>) {
return computed(
() => {
@@ -96,10 +109,6 @@ export const useAdminHouseholds = function () {
);
}
if (!households.value) {
getAllHouseholds();
}
return {
households,
useHouseholdsInGroup,

View File

@@ -93,7 +93,7 @@ export const LOCALES = [
{
name: "Nederlands (Dutch)",
value: "nl-NL",
progress: 42,
progress: 39,
dir: "ltr",
},
{
@@ -135,7 +135,7 @@ export const LOCALES = [
{
name: "Magyar (Hungarian)",
value: "hu-HU",
progress: 39,
progress: 38,
dir: "ltr",
},
{
@@ -213,7 +213,7 @@ export const LOCALES = [
{
name: "Deutsch (German)",
value: "de-DE",
progress: 55,
progress: 46,
dir: "ltr",
},
{

View File

@@ -1,47 +1,47 @@
/* eslint-disable @typescript-eslint/no-require-imports */
const datetimeFormats = {
// CODE_GEN_ID: DATE_LOCALES
"tr-TR": require("./lang/dateTimeFormats/tr-TR.json"),
"zh-CN": require("./lang/dateTimeFormats/zh-CN.json"),
"ja-JP": require("./lang/dateTimeFormats/ja-JP.json"),
"en-GB": require("./lang/dateTimeFormats/en-GB.json"),
"ca-ES": require("./lang/dateTimeFormats/ca-ES.json"),
"it-IT": require("./lang/dateTimeFormats/it-IT.json"),
"pl-PL": require("./lang/dateTimeFormats/pl-PL.json"),
"pt-PT": require("./lang/dateTimeFormats/pt-PT.json"),
"ro-RO": require("./lang/dateTimeFormats/ro-RO.json"),
"sr-SP": require("./lang/dateTimeFormats/sr-SP.json"),
"hr-HR": require("./lang/dateTimeFormats/hr-HR.json"),
"de-DE": require("./lang/dateTimeFormats/de-DE.json"),
"zh-TW": require("./lang/dateTimeFormats/zh-TW.json"),
"af-ZA": require("./lang/dateTimeFormats/af-ZA.json"),
"fr-CA": require("./lang/dateTimeFormats/fr-CA.json"),
"he-IL": require("./lang/dateTimeFormats/he-IL.json"),
"pt-BR": require("./lang/dateTimeFormats/pt-BR.json"),
"cs-CZ": require("./lang/dateTimeFormats/cs-CZ.json"),
"fr-FR": require("./lang/dateTimeFormats/fr-FR.json"),
"ru-RU": require("./lang/dateTimeFormats/ru-RU.json"),
"is-IS": require("./lang/dateTimeFormats/is-IS.json"),
"sk-SK": require("./lang/dateTimeFormats/sk-SK.json"),
"el-GR": require("./lang/dateTimeFormats/el-GR.json"),
"fr-BE": require("./lang/dateTimeFormats/fr-BE.json"),
"da-DK": require("./lang/dateTimeFormats/da-DK.json"),
"hu-HU": require("./lang/dateTimeFormats/hu-HU.json"),
"es-ES": require("./lang/dateTimeFormats/es-ES.json"),
"gl-ES": require("./lang/dateTimeFormats/gl-ES.json"),
"no-NO": require("./lang/dateTimeFormats/no-NO.json"),
"lt-LT": require("./lang/dateTimeFormats/lt-LT.json"),
"nl-NL": require("./lang/dateTimeFormats/nl-NL.json"),
"pl-PL": require("./lang/dateTimeFormats/pl-PL.json"),
"da-DK": require("./lang/dateTimeFormats/da-DK.json"),
"fr-CA": require("./lang/dateTimeFormats/fr-CA.json"),
"fr-BE": require("./lang/dateTimeFormats/fr-BE.json"),
"it-IT": require("./lang/dateTimeFormats/it-IT.json"),
"sl-SI": require("./lang/dateTimeFormats/sl-SI.json"),
"sr-SP": require("./lang/dateTimeFormats/sr-SP.json"),
"is-IS": require("./lang/dateTimeFormats/is-IS.json"),
"ja-JP": require("./lang/dateTimeFormats/ja-JP.json"),
"fr-FR": require("./lang/dateTimeFormats/fr-FR.json"),
"ca-ES": require("./lang/dateTimeFormats/ca-ES.json"),
"tr-TR": require("./lang/dateTimeFormats/tr-TR.json"),
"fi-FI": require("./lang/dateTimeFormats/fi-FI.json"),
"hr-HR": require("./lang/dateTimeFormats/hr-HR.json"),
"pt-BR": require("./lang/dateTimeFormats/pt-BR.json"),
"sk-SK": require("./lang/dateTimeFormats/sk-SK.json"),
"zh-CN": require("./lang/dateTimeFormats/zh-CN.json"),
"pt-PT": require("./lang/dateTimeFormats/pt-PT.json"),
"en-GB": require("./lang/dateTimeFormats/en-GB.json"),
"ro-RO": require("./lang/dateTimeFormats/ro-RO.json"),
"cs-CZ": require("./lang/dateTimeFormats/cs-CZ.json"),
"en-US": require("./lang/dateTimeFormats/en-US.json"),
"sv-SE": require("./lang/dateTimeFormats/sv-SE.json"),
"lv-LV": require("./lang/dateTimeFormats/lv-LV.json"),
"ko-KR": require("./lang/dateTimeFormats/ko-KR.json"),
"bg-BG": require("./lang/dateTimeFormats/bg-BG.json"),
"sl-SI": require("./lang/dateTimeFormats/sl-SI.json"),
"uk-UA": require("./lang/dateTimeFormats/uk-UA.json"),
"lv-LV": require("./lang/dateTimeFormats/lv-LV.json"),
"gl-ES": require("./lang/dateTimeFormats/gl-ES.json"),
"de-DE": require("./lang/dateTimeFormats/de-DE.json"),
"lt-LT": require("./lang/dateTimeFormats/lt-LT.json"),
"ru-RU": require("./lang/dateTimeFormats/ru-RU.json"),
"he-IL": require("./lang/dateTimeFormats/he-IL.json"),
"el-GR": require("./lang/dateTimeFormats/el-GR.json"),
"zh-TW": require("./lang/dateTimeFormats/zh-TW.json"),
"af-ZA": require("./lang/dateTimeFormats/af-ZA.json"),
"es-ES": require("./lang/dateTimeFormats/es-ES.json"),
"sv-SE": require("./lang/dateTimeFormats/sv-SE.json"),
"ar-SA": require("./lang/dateTimeFormats/ar-SA.json"),
"nl-NL": require("./lang/dateTimeFormats/nl-NL.json"),
"vi-VN": require("./lang/dateTimeFormats/vi-VN.json"),
"fi-FI": require("./lang/dateTimeFormats/fi-FI.json"),
"uk-UA": require("./lang/dateTimeFormats/uk-UA.json"),
// END: DATE_LOCALES
};

View File

@@ -599,7 +599,7 @@
"create-recipe-from-an-image": "Créer une recette à partir dune image",
"create-recipe-from-an-image-description": "Créez une recette en téléchargeant une image de celle-ci. Mealie utilisera lIA pour tenter dextraire le texte et de créer une recette.",
"crop-and-rotate-the-image": "Rogner et pivoter limage pour que seul le texte soit visible, et quil soit dans la bonne orientation.",
"create-from-images": "Créer à partir dimages",
"create-from-images": "Create from Images",
"should-translate-description": "Traduire la recette dans ma langue",
"please-wait-image-procesing": "Veuillez patienter, limage est en cours de traitement. Cela peut prendre du temps.",
"please-wait-images-processing": "Please wait, the images are processing. This may take some time.",

View File

@@ -241,48 +241,48 @@ export default defineNuxtConfig({
i18n: {
locales: [
// CODE_GEN_ID: MESSAGE_LOCALES
{ code: "tr-TR", file: "tr-TR.ts" },
{ code: "zh-CN", file: "zh-CN.ts" },
{ code: "ja-JP", file: "ja-JP.ts" },
{ code: "en-GB", file: "en-GB.ts" },
{ code: "ca-ES", file: "ca-ES.ts" },
{ code: "it-IT", file: "it-IT.ts" },
{ code: "pl-PL", file: "pl-PL.ts" },
{ code: "pt-PT", file: "pt-PT.ts" },
{ code: "ro-RO", file: "ro-RO.ts" },
{ code: "sr-SP", file: "sr-SP.ts" },
{ code: "hr-HR", file: "hr-HR.ts" },
{ code: "de-DE", file: "de-DE.ts" },
{ code: "zh-TW", file: "zh-TW.ts" },
{ code: "af-ZA", file: "af-ZA.ts" },
{ code: "fr-CA", file: "fr-CA.ts" },
{ code: "he-IL", file: "he-IL.ts" },
{ code: "pt-BR", file: "pt-BR.ts" },
{ code: "cs-CZ", file: "cs-CZ.ts" },
{ code: "fr-FR", file: "fr-FR.ts" },
{ code: "ru-RU", file: "ru-RU.ts" },
{ code: "is-IS", file: "is-IS.ts" },
{ code: "sk-SK", file: "sk-SK.ts" },
{ code: "el-GR", file: "el-GR.ts" },
{ code: "fr-BE", file: "fr-BE.ts" },
{ code: "da-DK", file: "da-DK.ts" },
{ code: "hu-HU", file: "hu-HU.ts" },
{ code: "es-ES", file: "es-ES.ts" },
{ code: "gl-ES", file: "gl-ES.ts" },
{ code: "no-NO", file: "no-NO.ts" },
{ code: "lt-LT", file: "lt-LT.ts" },
{ code: "nl-NL", file: "nl-NL.ts" },
{ code: "pl-PL", file: "pl-PL.ts" },
{ code: "da-DK", file: "da-DK.ts" },
{ code: "fr-CA", file: "fr-CA.ts" },
{ code: "fr-BE", file: "fr-BE.ts" },
{ code: "it-IT", file: "it-IT.ts" },
{ code: "sl-SI", file: "sl-SI.ts" },
{ code: "sr-SP", file: "sr-SP.ts" },
{ code: "is-IS", file: "is-IS.ts" },
{ code: "ja-JP", file: "ja-JP.ts" },
{ code: "fr-FR", file: "fr-FR.ts" },
{ code: "ca-ES", file: "ca-ES.ts" },
{ code: "tr-TR", file: "tr-TR.ts" },
{ code: "fi-FI", file: "fi-FI.ts" },
{ code: "hr-HR", file: "hr-HR.ts" },
{ code: "pt-BR", file: "pt-BR.ts" },
{ code: "sk-SK", file: "sk-SK.ts" },
{ code: "zh-CN", file: "zh-CN.ts" },
{ code: "pt-PT", file: "pt-PT.ts" },
{ code: "en-GB", file: "en-GB.ts" },
{ code: "ro-RO", file: "ro-RO.ts" },
{ code: "cs-CZ", file: "cs-CZ.ts" },
{ code: "et-EE", file: "et-EE.ts" },
{ code: "en-US", file: "en-US.ts" },
{ code: "sv-SE", file: "sv-SE.ts" },
{ code: "lv-LV", file: "lv-LV.ts" },
{ code: "ko-KR", file: "ko-KR.ts" },
{ code: "bg-BG", file: "bg-BG.ts" },
{ code: "sl-SI", file: "sl-SI.ts" },
{ code: "uk-UA", file: "uk-UA.ts" },
{ code: "et-EE", file: "et-EE.ts" },
{ code: "lv-LV", file: "lv-LV.ts" },
{ code: "gl-ES", file: "gl-ES.ts" },
{ code: "de-DE", file: "de-DE.ts" },
{ code: "lt-LT", file: "lt-LT.ts" },
{ code: "ru-RU", file: "ru-RU.ts" },
{ code: "he-IL", file: "he-IL.ts" },
{ code: "el-GR", file: "el-GR.ts" },
{ code: "zh-TW", file: "zh-TW.ts" },
{ code: "af-ZA", file: "af-ZA.ts" },
{ code: "es-ES", file: "es-ES.ts" },
{ code: "sv-SE", file: "sv-SE.ts" },
{ code: "ar-SA", file: "ar-SA.ts" },
{ code: "nl-NL", file: "nl-NL.ts" },
{ code: "vi-VN", file: "vi-VN.ts" },
{ code: "fi-FI", file: "fi-FI.ts" },
{ code: "uk-UA", file: "uk-UA.ts" },
// END: MESSAGE_LOCALES
],
strategy: "no_prefix",

View File

@@ -14,6 +14,7 @@
:items="groups"
item-title="name"
item-value="id"
:return-object="false"
variant="filled"
:label="$t('household.household-group')"
:rules="[validators.required]"
@@ -93,7 +94,10 @@
icon
color="error"
variant="text"
@click.stop="confirmDialog = true; deleteTarget = item.id"
@click.stop="
confirmDialog = true;
deleteTarget = +item.id;
"
>
<v-icon>
{{ $globals.icons.delete }}
@@ -110,7 +114,7 @@
</v-container>
</template>
<script setup lang="ts">
<script lang="ts">
import { fieldTypes } from "~/composables/forms";
import { useGroups } from "~/composables/use-groups";
import { useAdminHouseholds } from "~/composables/use-households";
@@ -118,73 +122,92 @@ import { validators } from "~/composables/use-validators";
import type { HouseholdInDB } from "~/lib/api/types/household";
import type { VForm } from "~/types/auto-forms";
definePageMeta({
layout: "admin",
});
export default defineNuxtComponent({
setup() {
definePageMeta({
layout: "admin",
});
const i18n = useI18n();
const i18n = useI18n();
useSeoMeta({
title: i18n.t("household.manage-households"),
});
// Set page title
useSeoMeta({
title: i18n.t("household.manage-households"),
});
const { groups } = useGroups();
const { households, deleteHousehold, createHousehold } = useAdminHouseholds();
const { groups } = useGroups();
const { households, refreshAllHouseholds, deleteHousehold, createHousehold } = useAdminHouseholds();
const refNewHouseholdForm = ref<VForm | null>(null);
const refNewHouseholdForm = ref<VForm | null>(null);
const state = reactive({
createDialog: false,
confirmDialog: false,
loading: false,
deleteTarget: 0,
search: "",
headers: [
{
title: i18n.t("household.household"),
align: "start",
sortable: false,
value: "id",
},
{ title: i18n.t("general.name"), value: "name" },
{ title: i18n.t("group.group"), value: "group" },
{ title: i18n.t("user.total-users"), value: "users" },
{ title: i18n.t("user.webhooks-enabled"), value: "webhookEnable" },
{ title: i18n.t("general.delete"), value: "actions" },
],
updateMode: false,
createHouseholdForm: {
items: [
{
label: i18n.t("household.household-name"),
varName: "name",
type: fieldTypes.TEXT,
rules: ["required"],
},
],
data: {
groupId: "",
name: "",
},
},
});
const createDialog = ref(false);
const confirmDialog = ref(false);
const deleteTarget = ref<string>("");
const search = ref("");
const updateMode = ref(false);
function openDialog() {
state.createDialog = true;
state.createHouseholdForm.data.name = "";
state.createHouseholdForm.data.groupId = "";
}
const headers = [
{
title: i18n.t("household.household"),
align: "start",
sortable: false,
value: "id",
},
{ title: i18n.t("general.name"), value: "name" },
{ title: i18n.t("group.group"), value: "group" },
{ title: i18n.t("user.total-users"), value: "users" },
{ title: i18n.t("user.webhooks-enabled"), value: "webhookEnable" },
{ title: i18n.t("general.delete"), value: "actions" },
];
const router = useRouter();
const createHouseholdForm = reactive({
items: [
{
label: i18n.t("household.household-name"),
varName: "name",
type: fieldTypes.TEXT,
rules: ["required"],
},
],
data: {
groupId: "",
name: "",
function handleRowClick(item: HouseholdInDB) {
router.push(`/admin/manage/households/${item.id}`);
}
async function handleCreateSubmit() {
if (!refNewHouseholdForm.value?.validate()) {
return;
}
state.createDialog = false;
await createHousehold(state.createHouseholdForm.data);
}
return {
...toRefs(state),
refNewHouseholdForm,
groups,
households,
validators,
refreshAllHouseholds,
deleteHousehold,
handleCreateSubmit,
openDialog,
handleRowClick,
};
},
});
function openDialog() {
createDialog.value = true;
createHouseholdForm.data.name = "";
createHouseholdForm.data.groupId = "";
}
const router = useRouter();
function handleRowClick(item: HouseholdInDB) {
router.push(`/admin/manage/households/${item.id}`);
}
async function handleCreateSubmit() {
if (!refNewHouseholdForm.value?.validate()) {
return;
}
createDialog.value = false;
await createHousehold(createHouseholdForm.data);
}
</script>

View File

@@ -21,23 +21,26 @@
<v-card variant="outlined">
<v-card-text>
<v-select
v-model="selectedGroup"
:items="groups || []"
v-if="groups"
v-model="selectedGroupId"
:items="groups"
item-title="name"
return-object
item-value="id"
:return-object="false"
variant="filled"
:label="$t('group.user-group')"
:rules="[validators.required]"
/>
<v-select
v-model="newUserData.household"
:disabled="!selectedGroup"
:disabled="!selectedGroupId"
:items="households"
item-title="name"
item-value="name"
:return-object="false"
variant="filled"
:label="$t('household.user-household')"
:hint="selectedGroup ? '' : $t('group.you-must-select-a-group-before-selecting-a-household')"
:hint="selectedGroupId ? '' : $t('group.you-must-select-a-group-before-selecting-a-household')"
persistent-hint
:rules="[validators.required]"
/>
@@ -57,51 +60,82 @@
</v-container>
</template>
<script setup lang="ts">
<script lang="ts">
import { useAdminApi } from "~/composables/api";
import { useGroups } from "~/composables/use-groups";
import { useAdminHouseholds } from "~/composables/use-households";
import { useUserForm } from "~/composables/use-users";
import { validators } from "~/composables/use-validators";
import type { GroupInDB, UserIn } from "~/lib/api/types/user";
import type { UserIn } from "~/lib/api/types/user";
import type { VForm } from "~/types/auto-forms";
definePageMeta({
layout: "admin",
export default defineNuxtComponent({
setup() {
definePageMeta({
layout: "admin",
});
const { userForm } = useUserForm();
const { groups } = useGroups();
const { useHouseholdsInGroup } = useAdminHouseholds();
const router = useRouter();
// ==============================================
// New User Form
const refNewUserForm = ref<VForm | null>(null);
const adminApi = useAdminApi();
const selectedGroupId = ref<string>("");
const households = useHouseholdsInGroup(selectedGroupId);
const selectedGroup = computed(() => {
return groups.value?.find(group => group.id === selectedGroupId.value);
});
const state = reactive({
newUserData: {
username: "",
fullName: "",
email: "",
admin: false,
group: selectedGroup.value?.name || "",
household: "",
advanced: false,
canInvite: false,
canManage: false,
canOrganize: false,
password: "",
authMethod: "Mealie",
},
});
watch(selectedGroup, (newGroup) => {
state.newUserData.group = newGroup?.name || "";
state.newUserData.household = "";
});
async function handleSubmit() {
if (!refNewUserForm.value?.validate()) return;
const { response } = await adminApi.users.createOne(state.newUserData as UserIn);
if (response?.status === 201) {
router.push("/admin/manage/users");
}
}
return {
...toRefs(state),
userForm,
refNewUserForm,
handleSubmit,
groups,
selectedGroupId,
households,
validators,
};
},
});
const { userForm } = useUserForm();
const { groups } = useGroups();
const router = useRouter();
const refNewUserForm = ref<VForm | null>(null);
const adminApi = useAdminApi();
const selectedGroup = ref<GroupInDB | undefined>(undefined);
const households = computed(() => selectedGroup.value?.households || []);
const newUserData = ref({
username: "",
fullName: "",
email: "",
admin: false,
group: computed(() => selectedGroup.value?.name || ""),
household: "",
advanced: false,
canInvite: false,
canManage: false,
canOrganize: false,
password: "",
authMethod: "Mealie",
});
async function handleSubmit() {
if (!refNewUserForm.value?.validate()) return;
const { response } = await adminApi.users.createOne(newUserData.value as UserIn);
if (response?.status === 201) {
router.push("/admin/manage/users");
}
}
</script>
<style lang="scss" scoped></style>

View File

@@ -9,5 +9,11 @@ import RecipeExplorerPage from "~/components/Domain/Recipe/RecipeExplorerPage.vu
export default defineNuxtComponent({
components: { RecipeExplorerPage },
setup() {
// Enable scroll restoration for this page to work with our state management
definePageMeta({
scrollToTop: false,
});
},
});
</script>

View File

File diff suppressed because it is too large Load Diff

View File

@@ -4,31 +4,31 @@
"garlic": {
"aliases": [],
"description": "",
"name": "fokhagyma",
"plural_name": "fokhagyma"
"name": "garlic",
"plural_name": "garlics"
},
"onion": {
"aliases": [],
"description": "",
"name": "hagyma",
"plural_name": "hagyma"
"name": "onion",
"plural_name": "onions"
},
"bell pepper": {
"aliases": [],
"description": "",
"name": "kaliforniai paprika",
"plural_name": "kaliforniai paprika"
"name": "bell pepper",
"plural_name": "bell peppers"
},
"carrot": {
"aliases": [],
"description": "",
"name": "sárgarépa",
"plural_name": "sárgarépa"
"name": "carrot",
"plural_name": "carrots"
},
"scallion": {
"aliases": [],
"description": "",
"name": "újhagyma",
"name": "scallion",
"plural_name": "zöldhagyma"
},
"zucchini": {
@@ -52,8 +52,8 @@
"yellow onion": {
"aliases": [],
"description": "",
"name": "vöröshagyma",
"plural_name": "vöröshagyma"
"name": "yellow onion",
"plural_name": "yellow onions"
},
"celery": {
"aliases": [],
@@ -82,8 +82,8 @@
"cherry tomato": {
"aliases": [],
"description": "",
"name": "koktélparadicsom",
"plural_name": "koktélparadicsom"
"name": "cherry tomato",
"plural_name": "cherry tomatoes"
},
"cucumber": {
"aliases": [],
@@ -105,11 +105,11 @@
},
"chile pepper": {
"aliases": [
"kaliforniai paprika"
"capsicum"
],
"description": "",
"name": "chili paprika",
"plural_name": "chili paprika"
"name": "chile pepper",
"plural_name": "chile peppers"
},
"sweet potato": {
"aliases": [],
@@ -126,20 +126,20 @@
"heart of palm": {
"aliases": [],
"description": "",
"name": "pálmarügy",
"plural_name": "pálmarügy"
"name": "heart of palm",
"plural_name": "heart of palms"
},
"baby green": {
"aliases": [],
"description": "",
"name": "salátakeverék",
"plural_name": "salátakeverék"
"name": "baby green",
"plural_name": "baby greens"
},
"pumpkin": {
"aliases": [],
"description": "",
"name": "sütőtök",
"plural_name": "sütőtök"
"name": "pumpkin",
"plural_name": "pumpkins"
},
"cauliflower": {
"aliases": [],
@@ -151,258 +151,258 @@
"aliases": [],
"description": "",
"name": "káposzta",
"plural_name": "fejes káposzta"
"plural_name": "cabbages"
},
"asparagu": {
"aliases": [],
"description": "",
"name": "spárga",
"plural_name": "spárga"
"name": "asparagu",
"plural_name": "asparagus"
},
"kale": {
"aliases": [],
"description": "",
"name": "kelkáposzta",
"plural_name": "kelkáposzta"
"name": "kale",
"plural_name": "kales"
},
"arugula": {
"aliases": [],
"description": "",
"name": "rukkola",
"plural_name": "rukkola"
"name": "arugula",
"plural_name": "arugulas"
},
"leek": {
"aliases": [],
"description": "",
"name": "póréhagyma",
"plural_name": "póréhagyma"
"name": "leek",
"plural_name": "leeks"
},
"eggplant": {
"aliases": [],
"description": "",
"name": "padlizsán",
"plural_name": "padlizsán"
"name": "eggplant",
"plural_name": "eggplants"
},
"lettuce": {
"aliases": [],
"description": "",
"name": "saláta",
"plural_name": "saláta"
"name": "lettuce",
"plural_name": "lettuces"
},
"butternut squash": {
"aliases": [],
"description": "",
"name": "kanadai sütőtök",
"plural_name": "kanadai sütőtök"
"name": "butternut squash",
"plural_name": "butternut squashes"
},
"romaine": {
"aliases": [],
"description": "",
"name": "római saláta",
"plural_name": "római saláta"
"name": "romaine",
"plural_name": "romaines"
},
"beetroot": {
"aliases": [],
"description": "",
"name": "cékla",
"plural_name": "cékla"
"name": "beetroot",
"plural_name": "beetroots"
},
"brussels sprout": {
"aliases": [],
"description": "",
"name": "kelbimbó",
"plural_name": "kelbimbó"
"name": "brussels sprout",
"plural_name": "brussels sprouts"
},
"fennel": {
"aliases": [],
"description": "",
"name": "édeskömény",
"plural_name": "édeskömény"
"name": "fennel",
"plural_name": "fennels"
},
"sun dried tomato": {
"aliases": [],
"description": "",
"name": "szárított paradicsom",
"plural_name": "szárított paradicsom"
"name": "sun dried tomato",
"plural_name": "sun dried tomatoes"
},
"radish": {
"aliases": [],
"description": "",
"name": "retek",
"plural_name": "retkek"
"name": "radish",
"plural_name": "radishes"
},
"red cabbage": {
"aliases": [],
"description": "",
"name": "vöröskáposzta",
"plural_name": "vöröskáposzta"
"name": "red cabbage",
"plural_name": "red cabbages"
},
"artichoke": {
"aliases": [],
"description": "",
"name": "articsóka",
"plural_name": "articsóka"
"name": "artichoke",
"plural_name": "artichokes"
},
"new potato": {
"aliases": [],
"description": "",
"name": "újburgonya",
"plural_name": "újburgonya"
"name": "new potato",
"plural_name": "new potatoes"
},
"summer squash": {
"aliases": [
"cukkini",
"kis tök"
"courgette",
"gem squash"
],
"description": "",
"name": "főzőtök",
"plural_name": "főzőtök"
"name": "summer squash",
"plural_name": "summer squashes"
},
"mixed green": {
"aliases": [],
"description": "",
"name": "salátakeverék",
"plural_name": "salátakeverék"
"name": "mixed green",
"plural_name": "mixed greens"
},
"parsnip": {
"aliases": [],
"description": "",
"name": "paszternák",
"plural_name": "paszternák"
"name": "parsnip",
"plural_name": "parsnips"
},
"baby carrot": {
"aliases": [],
"description": "",
"name": "bébirépa",
"plural_name": "bébirépa"
"name": "baby carrot",
"plural_name": "baby carrots"
},
"mixed vegetable": {
"aliases": [],
"description": "",
"name": "zöldségkeverék",
"plural_name": "zöldségkeverék"
"name": "mixed vegetable",
"plural_name": "mixed vegetables"
},
"poblano pepper": {
"aliases": [],
"description": "",
"name": "poblano paprika",
"plural_name": "poblano paprika"
"name": "poblano pepper",
"plural_name": "poblano peppers"
},
"sweet pepper": {
"aliases": [],
"description": "",
"name": "édes paprika",
"plural_name": "édes paprika"
"name": "sweet pepper",
"plural_name": "sweet peppers"
},
"serrano pepper": {
"aliases": [],
"description": "",
"name": "serrano paprika",
"plural_name": "serrano paprika"
"name": "serrano pepper",
"plural_name": "serrano peppers"
},
"cayenne pepper": {
"aliases": [],
"description": "",
"name": "cayenne bors",
"plural_name": "cayenne bors"
"name": "cayenne pepper",
"plural_name": "cayenne peppers"
},
"green tomato": {
"aliases": [],
"description": "",
"name": "zöld paradicsom",
"plural_name": "zöld paradicsom"
"name": "green tomato",
"plural_name": "green tomatoes"
},
"watercress": {
"aliases": [],
"description": "",
"name": "vízitorma",
"plural_name": "vízitorma"
"name": "watercress",
"plural_name": "watercress"
},
"iceberg": {
"aliases": [],
"description": "",
"name": "jégsaláta",
"plural_name": "jégsaláta"
"name": "iceberg",
"plural_name": "icebergs"
},
"mashed potato": {
"aliases": [],
"description": "",
"name": "burgonyapüré",
"plural_name": "burgonyapüré"
"name": "mashed potato",
"plural_name": "mashed potatoes"
},
"horseradish": {
"aliases": [],
"description": "",
"name": "torma",
"plural_name": "torma"
"name": "horseradish",
"plural_name": "horseradishes"
},
"chard": {
"aliases": [],
"description": "",
"name": "mángold",
"plural_name": "mángold"
"name": "chard",
"plural_name": "chards"
},
"pimiento": {
"aliases": [],
"description": "",
"name": "kápia paprika",
"plural_name": "kápia paprika"
"name": "pimiento",
"plural_name": "pimientoes"
},
"spaghetti squash": {
"aliases": [],
"description": "",
"name": "spagettitök",
"plural_name": "spagettitök"
"name": "spaghetti squash",
"plural_name": "spaghetti squashes"
},
"butter lettuce": {
"aliases": [],
"description": "",
"name": "fejes saláta",
"plural_name": "fejes saláta"
"name": "butter lettuce",
"plural_name": "butter lettuces"
},
"hash brown": {
"aliases": [],
"description": "",
"name": "tócsni",
"plural_name": "tócsni"
"name": "hash brown",
"plural_name": "hash browns"
},
"napa cabbage": {
"aliases": [
"kínai kel"
"chinese leaves"
],
"description": "",
"name": "kínai kel",
"plural_name": "kínai kel"
"name": "napa cabbage",
"plural_name": "napa cabbages"
},
"celeriac": {
"aliases": [],
"description": "",
"name": "zeller",
"plural_name": "zeller"
"name": "celeriac",
"plural_name": "celeriacs"
},
"water chestnut": {
"aliases": [],
"description": "",
"name": "vízigesztenye",
"plural_name": "vízigesztenye"
"name": "water chestnut",
"plural_name": "water chestnuts"
},
"turnip": {
"aliases": [],
"description": "",
"name": "tarlórépa",
"plural_name": "tarlórépa"
"name": "turnip",
"plural_name": "turnips"
},
"thai chile pepper": {
"aliases": [],
"description": "",
"name": "thai csili paprika",
"plural_name": "thai csili paprika"
"name": "thai chile pepper",
"plural_name": "thai chile peppers"
},
"bok choy": {
"aliases": [],
"description": "",
"name": "bordáskel",
"plural_name": "bordáskel"
"name": "bok choy",
"plural_name": "bok choy"
},
"okra": {
"aliases": [],
@@ -413,44 +413,44 @@
"acorn squash": {
"aliases": [],
"description": "",
"name": "makktök",
"plural_name": "makktök"
"name": "acorn squash",
"plural_name": "acorn squashes"
},
"corn cob": {
"aliases": [],
"description": "",
"name": "kukoricacső",
"plural_name": "kukoricacső"
"name": "corn cob",
"plural_name": "corn cobs"
},
"radicchio": {
"aliases": [],
"description": "",
"name": "vörös cikória",
"plural_name": "vörös cikória"
"name": "radicchio",
"plural_name": "radicchio"
},
"pearl onion": {
"aliases": [],
"description": "",
"name": "gyöngyhagyma",
"plural_name": "gyöngyhagyma"
"name": "pearl onion",
"plural_name": "pearl onions"
},
"tenderstem broccoli": {
"aliases": [],
"description": "",
"name": "brokkolini",
"plural_name": "brokkolini"
"name": "tenderstem broccoli",
"plural_name": "tenderstem broccolis"
},
"plantain": {
"aliases": [],
"description": "",
"name": "főzőbanán",
"plural_name": "főzőbanán"
"name": "plantain",
"plural_name": "plantains"
},
"leaf lettuce": {
"aliases": [],
"description": "",
"name": "saláta",
"plural_name": "saláta"
"name": "leaf lettuce",
"plural_name": "leaf lettuces"
},
"pepperoncini": {
"aliases": [],
@@ -602,25 +602,25 @@
"aliases": [],
"description": "",
"name": "arbol chile pepper",
"plural_name": "arbol csili paprika"
"plural_name": "arbol chile peppers"
},
"golden beet": {
"aliases": [],
"description": "",
"name": "sárga cékla",
"plural_name": "sárga cékla"
"name": "golden beet",
"plural_name": "golden beets"
},
"pea shoot": {
"aliases": [],
"description": "",
"name": "borsócsíra",
"plural_name": "borsócsíra"
"name": "pea shoot",
"plural_name": "pea shoots"
},
"alfalfa": {
"aliases": [],
"description": "",
"name": "lucernacsíra",
"plural_name": "lucernacsíra"
"name": "alfalfa",
"plural_name": "alfalfas"
}
}
},
@@ -628,129 +628,129 @@
"foods": {
"tomato": {
"aliases": [],
"description": "Igen, gyümölcs",
"name": "paradicsom",
"plural_name": "paradicsom"
"description": "Yes they are a fruit",
"name": "tomato",
"plural_name": "tomatoes"
},
"lemon": {
"aliases": [],
"description": "",
"name": "citrom",
"plural_name": "citrom"
"name": "lemon",
"plural_name": "lemons"
},
"lime": {
"aliases": [],
"description": "",
"name": "zöldcitrom",
"plural_name": "zöldcitrom"
"name": "lime",
"plural_name": "limes"
},
"apple": {
"aliases": [],
"description": "",
"name": "alma",
"plural_name": "alma"
"name": "apple",
"plural_name": "apples"
},
"banana": {
"aliases": [],
"description": "",
"name": "banán",
"plural_name": "banán"
"name": "banana",
"plural_name": "bananas"
},
"orange": {
"aliases": [],
"description": "",
"name": "narancs",
"plural_name": "narancs"
"name": "orange",
"plural_name": "oranges"
},
"raisin": {
"aliases": [],
"description": "",
"name": "mazsola",
"plural_name": "mazsola"
"name": "raisin",
"plural_name": "raisins"
},
"pineapple": {
"aliases": [],
"description": "",
"name": "ananász",
"plural_name": "ananász"
"name": "pineapple",
"plural_name": "pineapples"
},
"mango": {
"aliases": [],
"description": "",
"name": "mangó",
"plural_name": "mangó"
"name": "mango",
"plural_name": "mangoes"
},
"peach": {
"aliases": [],
"description": "",
"name": "őszibarack",
"plural_name": "őszibarack"
"name": "peach",
"plural_name": "peaches"
},
"date": {
"aliases": [],
"description": "",
"name": "datolya",
"plural_name": "datolya"
"name": "date",
"plural_name": "dates"
},
"coconut": {
"aliases": [],
"description": "",
"name": "kókuszdió",
"plural_name": "kókuszdió"
"name": "coconut",
"plural_name": "coconuts"
},
"craisin": {
"aliases": [],
"description": "",
"name": "aszalt vörösáfonya",
"plural_name": "aszalt vörösáfonya"
"name": "craisin",
"plural_name": "craisins"
},
"pear": {
"aliases": [],
"description": "",
"name": "körte",
"plural_name": "körte"
"name": "pear",
"plural_name": "pears"
},
"grape": {
"aliases": [],
"description": "",
"name": "szőlő",
"plural_name": "szőlő"
"name": "grape",
"plural_name": "grapes"
},
"pomegranate": {
"aliases": [],
"description": "",
"name": "gránátalma",
"plural_name": "gránátalma"
"name": "pomegranate",
"plural_name": "pomegranates"
},
"watermelon": {
"aliases": [],
"description": "",
"name": "görögdinnye",
"plural_name": "görögdinnye"
"name": "watermelon",
"plural_name": "watermelons"
},
"rhubarb": {
"aliases": [],
"description": "",
"name": "rebarbara",
"plural_name": "rebarbara"
"name": "rhubarb",
"plural_name": "rhubarbs"
},
"dried apricot": {
"aliases": [],
"description": "",
"name": "aszalt sárgabarack",
"plural_name": "aszalt sárgabarack"
"name": "dried apricot",
"plural_name": "dried apricots"
},
"kiwi": {
"aliases": [],
"description": "",
"name": "kivi",
"plural_name": "kivi"
"name": "kiwi",
"plural_name": "kiwis"
},
"grapefruit": {
"aliases": [],
"description": "",
"name": "grapefruit",
"plural_name": "grapefruit"
"plural_name": "grapefruits"
},
"plum": {
"aliases": [],
@@ -905,86 +905,86 @@
"banana chip": {
"aliases": [],
"description": "",
"name": "banánchips",
"plural_name": "banánchips"
"name": "banana chip",
"plural_name": "banana chips"
},
"kumquat": {
"aliases": [],
"description": "",
"name": "törpemandarin",
"plural_name": "törpemandarin"
"name": "kumquat",
"plural_name": "kumquats"
},
"jackfruit": {
"aliases": [],
"description": "",
"name": "jackfruit",
"plural_name": "jackfruit"
"plural_name": "jackfruits"
},
"dragon fruit": {
"aliases": [],
"description": "",
"name": "sárkánygyümölcs",
"plural_name": "sárkánygyümölcs"
"name": "dragon fruit",
"plural_name": "dragon fruits"
},
"mixed fruit": {
"aliases": [],
"description": "",
"name": "vegyes gyümölcs",
"plural_name": "vegyes gyümölcs"
"name": "mixed fruit",
"plural_name": "mixed fruits"
},
"asian pear": {
"aliases": [],
"description": "",
"name": "japán körte",
"plural_name": "japán körte"
"name": "asian pear",
"plural_name": "asian pears"
},
"lychee": {
"aliases": [],
"description": "",
"name": "licsi",
"plural_name": "licsi"
"name": "lychee",
"plural_name": "lychees"
},
"young coconut": {
"aliases": [],
"description": "",
"name": "zsenge kókuszdió",
"plural_name": "zsenge kókuszdió"
"name": "young coconut",
"plural_name": "young coconuts"
},
"kaffir lime": {
"aliases": [],
"description": "",
"name": "kaffir lime",
"plural_name": "kaffir lime"
"plural_name": "kaffir limes"
},
"star fruit": {
"aliases": [],
"description": "",
"name": "csillaggyümölcs",
"plural_name": "csillaggyümölcs"
"name": "star fruit",
"plural_name": "star fruits"
},
"green papaya": {
"aliases": [],
"description": "",
"name": "zöld papaya",
"plural_name": "zöld papaya"
"name": "green papaya",
"plural_name": "green papayas"
},
"pomelo": {
"aliases": [],
"description": "",
"name": "pomelo",
"plural_name": "pomelo"
"plural_name": "pomeloes"
},
"chestnut puree": {
"aliases": [],
"description": "",
"name": "gesztenyepüré",
"plural_name": "gesztenyepüré"
"name": "chestnut puree",
"plural_name": "chestnut purees"
},
"prickly pear": {
"aliases": [],
"description": "",
"name": "kaktuszfüge",
"plural_name": "kaktuszfüge"
"name": "prickly pear",
"plural_name": "prickly pears"
},
"calamansi": {
"aliases": [],

View File

@@ -255,7 +255,7 @@
"edelpompoen"
],
"description": "",
"name": "edelpompoenen",
"name": "summer squash",
"plural_name": "edelpompoenen"
},
"mixed green": {
@@ -536,7 +536,7 @@
"aliases": [],
"description": "",
"name": "yamswortel",
"plural_name": "yamswortel"
"plural_name": "yams"
},
"ancho chile pepper": {
"aliases": [],
@@ -547,8 +547,8 @@
"microgreen": {
"aliases": [],
"description": "",
"name": "microgroen",
"plural_name": "kiemgroenten"
"name": "microgreen",
"plural_name": "microgreens"
},
"boston lettuce": {
"aliases": [],
@@ -559,55 +559,55 @@
"kohlrabi": {
"aliases": [],
"description": "",
"name": "koolrabi",
"plural_name": "koolrabi's"
"name": "kohlrabi",
"plural_name": "kohlrabis"
},
"fresno chile": {
"aliases": [],
"description": "",
"name": "fresno peper",
"plural_name": "fresno pepers"
"name": "fresno chile",
"plural_name": "fresno chiles"
},
"delicata squash": {
"aliases": [],
"description": "",
"name": "delicata pompoen",
"plural_name": "delicata pompoenen"
"name": "delicata squash",
"plural_name": "delicata squashes"
},
"frisee": {
"aliases": [],
"description": "",
"name": "krulandijvie",
"plural_name": "krulandijvie"
"name": "frisee",
"plural_name": "frisees"
},
"anaheim pepper": {
"aliases": [],
"description": "",
"name": "anaheim peper",
"plural_name": "anaheim pepers"
"name": "anaheim pepper",
"plural_name": "anaheim peppers"
},
"cres": {
"aliases": [],
"description": "",
"name": "kropsla",
"plural_name": "kropsla"
"name": "cres",
"plural_name": "cress"
},
"broccoli slaw": {
"aliases": [],
"description": "",
"name": "broccoli salade",
"plural_name": "broccoli salades"
"name": "broccoli slaw",
"plural_name": "broccoli slaws"
},
"arbol chile pepper": {
"aliases": [],
"description": "",
"name": "arbol peper",
"plural_name": "arbol pepers"
"name": "arbol chile pepper",
"plural_name": "arbol chile peppers"
},
"golden beet": {
"aliases": [],
"description": "",
"name": "gele biet",
"name": "golden beet",
"plural_name": "gele biet"
},
"pea shoot": {
@@ -628,8 +628,8 @@
"foods": {
"tomato": {
"aliases": [],
"description": "Ja, ze zijn fruit",
"name": "tomaat",
"description": "Yes they are a fruit",
"name": "tomato",
"plural_name": "tomaten"
},
"lemon": {
@@ -839,14 +839,14 @@
"meyer lemon": {
"aliases": [],
"description": "",
"name": "meyer citroen",
"plural_name": "meyer citroenen"
"name": "meyer lemon",
"plural_name": "meyer lemons"
},
"honeydew melon": {
"aliases": [],
"description": "",
"name": "honing meloen",
"plural_name": "honing meloenen"
"name": "honeydew melon",
"plural_name": "honeydew melons"
},
"dried fruit": {
"aliases": [],
@@ -863,50 +863,50 @@
"persimmon": {
"aliases": [],
"description": "",
"name": "kakivrucht",
"plural_name": "kakivruchten"
"name": "persimmon",
"plural_name": "persimmons"
},
"melon": {
"aliases": [],
"description": "",
"name": "meloen",
"plural_name": "meloenen"
"name": "melon",
"plural_name": "melons"
},
"tangerine": {
"aliases": [],
"description": "",
"name": "mandarijn",
"plural_name": "mandarijnen"
"name": "tangerine",
"plural_name": "tangerines"
},
"dried mango": {
"aliases": [],
"description": "",
"name": "gedroogde mango",
"plural_name": "gedroogde mango's"
"name": "dried mango",
"plural_name": "dried mangoes"
},
"dried apple": {
"aliases": [],
"description": "",
"name": "gedroogde appel",
"plural_name": "gedroogde appels"
"name": "dried apple",
"plural_name": "dried apples"
},
"quince": {
"aliases": [],
"description": "",
"name": "kweepeer",
"plural_name": "kweeperen"
"name": "quince",
"plural_name": "quinces"
},
"guava": {
"aliases": [],
"description": "",
"name": "guave",
"plural_name": "guava's"
"name": "guava",
"plural_name": "guavas"
},
"banana chip": {
"aliases": [],
"description": "",
"name": "bananen chip",
"plural_name": "bananen chips"
"name": "banana chip",
"plural_name": "banana chips"
},
"kumquat": {
"aliases": [],
@@ -923,20 +923,20 @@
"dragon fruit": {
"aliases": [],
"description": "",
"name": "drakenfruit",
"plural_name": "drakenfruit vruchten"
"name": "dragon fruit",
"plural_name": "dragon fruits"
},
"mixed fruit": {
"aliases": [],
"description": "",
"name": "gemixte vruchten",
"plural_name": "gemengde vruchten"
"name": "mixed fruit",
"plural_name": "mixed fruits"
},
"asian pear": {
"aliases": [],
"description": "",
"name": "aziatische peer",
"plural_name": "aziatische peren"
"name": "asian pear",
"plural_name": "asian pears"
},
"lychee": {
"aliases": [],
@@ -947,44 +947,44 @@
"young coconut": {
"aliases": [],
"description": "",
"name": "jonge kokokosnoot",
"plural_name": "jonge kokokosnoten"
"name": "young coconut",
"plural_name": "young coconuts"
},
"kaffir lime": {
"aliases": [],
"description": "",
"name": "kaffir limoen",
"plural_name": "kaffir limoenen"
"name": "kaffir lime",
"plural_name": "kaffir limes"
},
"star fruit": {
"aliases": [],
"description": "",
"name": "stervrucht",
"plural_name": "stervruchten"
"name": "star fruit",
"plural_name": "star fruits"
},
"green papaya": {
"aliases": [],
"description": "",
"name": "groene papaya",
"plural_name": "groene papaya's"
"name": "green papaya",
"plural_name": "green papayas"
},
"pomelo": {
"aliases": [],
"description": "",
"name": "pompelmoes",
"plural_name": "pompelmoezen"
"name": "pomelo",
"plural_name": "pomeloes"
},
"chestnut puree": {
"aliases": [],
"description": "",
"name": "kastanje puree",
"plural_name": "kastanje puree"
"name": "chestnut puree",
"plural_name": "chestnut purees"
},
"prickly pear": {
"aliases": [],
"description": "",
"name": "cactusvijg",
"plural_name": "cactusvijgen"
"name": "prickly pear",
"plural_name": "prickly pears"
},
"calamansi": {
"aliases": [],
@@ -1055,56 +1055,56 @@
"dried lemon": {
"aliases": [],
"description": "",
"name": "gedroogde limoen",
"plural_name": "gedroogde limoenen"
"name": "dried lemon",
"plural_name": "dried lemons"
},
"young jackfruit": {
"aliases": [],
"description": "",
"name": "jonge jackfruit",
"plural_name": "jonge jackfruits"
"name": "young jackfruit",
"plural_name": "young jackfruits"
},
"durian": {
"aliases": [],
"description": "",
"name": "doerian",
"plural_name": "doerians"
"name": "durian",
"plural_name": "durians"
},
"freeze-dried apple": {
"aliases": [],
"description": "",
"name": "gevriesdroogde appel",
"plural_name": "gevriesdroogde appels"
"name": "freeze-dried apple",
"plural_name": "freeze-dried apples"
},
"dried tamarind": {
"aliases": [],
"description": "",
"name": "gedroogte tamarinde",
"plural_name": "gedroogde tamarindes"
"name": "dried tamarind",
"plural_name": "dried tamarinds"
},
"honey date": {
"aliases": [],
"description": "",
"name": "honing dadel",
"plural_name": "honing dadels"
"name": "honey date",
"plural_name": "honey dates"
},
"physali": {
"aliases": [],
"description": "",
"name": "ananaskers",
"plural_name": "ananaskersen"
"name": "physali",
"plural_name": "physalis"
},
"tamarillo": {
"aliases": [],
"description": "",
"name": "tamarillo",
"plural_name": "tamarillo's"
"plural_name": "tamarilloes"
},
"ice-apple": {
"aliases": [],
"description": "",
"name": "ijsappel",
"plural_name": "ijsappels"
"name": "ice-apple",
"plural_name": "ice-apples"
},
"longan": {
"aliases": [],
@@ -1115,98 +1115,98 @@
"finger lime": {
"aliases": [],
"description": "",
"name": "citroenkaviaar",
"plural_name": "citroenkaviaar"
"name": "finger lime",
"plural_name": "finger limes"
},
"bitter orange": {
"aliases": [],
"description": "",
"name": "bittersinaasappel",
"plural_name": "bittersinaasappels"
"name": "bitter orange",
"plural_name": "bitter oranges"
},
"feijoa": {
"aliases": [],
"description": "",
"name": "ananasguave",
"plural_name": "ananasguaves"
"name": "feijoa",
"plural_name": "feijoas"
},
"dried persimmon": {
"aliases": [],
"description": "",
"name": "gedroogde kakivrucht",
"plural_name": "gedroogde kakivruchten"
"name": "dried persimmon",
"plural_name": "dried persimmons"
},
"rambutan": {
"aliases": [],
"description": "",
"name": "ramboetan",
"plural_name": "ramboetans"
"name": "rambutan",
"plural_name": "rambutans"
},
"rose apple": {
"aliases": [],
"description": "",
"name": "rozenappel",
"plural_name": "rozenappels"
"name": "rose apple",
"plural_name": "rose apples"
},
"dried orange slice": {
"aliases": [],
"description": "",
"name": "gedroogd sinaasappel partje",
"plural_name": "gedroogdesinaasappel partjes"
"name": "dried orange slice",
"plural_name": "dried orange slices"
},
"loquat": {
"aliases": [],
"description": "",
"name": "japanse pruim",
"plural_name": "japanse pruimen"
"name": "loquat",
"plural_name": "loquats"
},
"crabapple": {
"aliases": [],
"description": "",
"name": "wilde appel",
"plural_name": "wilde appels"
"name": "crabapple",
"plural_name": "crabapples"
},
"fig leaf": {
"aliases": [],
"description": "",
"name": "vijgenblad",
"plural_name": "vijgenbladeren"
"name": "fig leaf",
"plural_name": "fig leaves"
},
"freeze-dried pineapple": {
"aliases": [],
"description": "",
"name": "gevriesdroogde ananas",
"plural_name": "gevriesdroogde ananas"
"name": "freeze-dried pineapple",
"plural_name": "freeze-dried pineapples"
},
"pluot": {
"aliases": [],
"description": "",
"name": "pruim-abrikoos",
"plural_name": "pruim-abrikozen"
"name": "pluot",
"plural_name": "pluots"
},
"soursop": {
"aliases": [],
"description": "",
"name": "zuurzak",
"plural_name": "zuurzakken"
"name": "soursop",
"plural_name": "soursops"
},
"hog plum": {
"aliases": [],
"description": "",
"name": "varkenspruim",
"plural_name": "varkenspruimen"
"name": "hog plum",
"plural_name": "hog plums"
},
"bergamot orange": {
"aliases": [],
"description": "",
"name": "bergamot",
"plural_name": "bergamotten"
"name": "bergamot orange",
"plural_name": "bergamot oranges"
},
"luo han guo": {
"aliases": [],
"description": "",
"name": "monniksfruit",
"plural_name": "monniksfruit"
"name": "luo han guo",
"plural_name": "luo han guos"
},
"mamey": {
"aliases": [],
@@ -1223,14 +1223,14 @@
"green ume plum": {
"aliases": [],
"description": "",
"name": "groene ume pruim",
"plural_name": "groene ume pruimen"
"name": "green ume plum",
"plural_name": "green ume plums"
},
"kiwano": {
"aliases": [],
"description": "",
"name": "hoornmeloen",
"plural_name": "hoornmeloenen"
"name": "kiwano",
"plural_name": "kiwanoes"
}
}
},
@@ -1239,104 +1239,104 @@
"button mushroom": {
"aliases": [],
"description": "",
"name": "champignon",
"plural_name": "champignons"
"name": "button mushroom",
"plural_name": "button mushrooms"
},
"shiitake mushroom": {
"aliases": [],
"description": "",
"name": "shiitake",
"plural_name": "shiitakes"
"name": "shiitake mushroom",
"plural_name": "shiitake mushrooms"
},
"portobello mushroom": {
"aliases": [],
"description": "",
"name": "portobello",
"plural_name": "portobello's"
"name": "portobello mushroom",
"plural_name": "portobello mushrooms"
},
"wild mushroom": {
"aliases": [],
"description": "",
"name": "wilde paddenstoel",
"plural_name": "wilde paddenstoelen"
"name": "wild mushroom",
"plural_name": "wild mushrooms"
},
"porcini": {
"aliases": [],
"description": "",
"name": "eekhoorntjesbrood",
"plural_name": "eekhoorntjesbrood"
"name": "porcini",
"plural_name": "porcinis"
},
"mixed mushroom": {
"aliases": [],
"description": "",
"name": "gemengde paddenstoelen",
"plural_name": "gemengde paddenstoelen"
"name": "mixed mushroom",
"plural_name": "mixed mushrooms"
},
"oyster mushroom": {
"aliases": [],
"description": "",
"name": "oesterzwam",
"plural_name": "oesterzwammen"
"name": "oyster mushroom",
"plural_name": "oyster mushrooms"
},
"chestnut mushroom": {
"aliases": [],
"description": "",
"name": "kastanje champignon",
"plural_name": "kastanje champignons"
"name": "chestnut mushroom",
"plural_name": "chestnut mushrooms"
},
"enoki mushroom": {
"aliases": [],
"description": "",
"name": "enoki-paddenstoel",
"plural_name": "enoki-paddenstoeltjes"
"name": "enoki mushroom",
"plural_name": "enoki mushrooms"
},
"black fungu": {
"aliases": [],
"description": "",
"name": "boomoor",
"plural_name": "boomoren"
"name": "black fungu",
"plural_name": "black fungus"
},
"black truffle": {
"aliases": [],
"description": "",
"name": "zwarte truffel",
"plural_name": "zwarte truffels"
"name": "black truffle",
"plural_name": "black truffles"
},
"morel mushroom": {
"aliases": [],
"description": "",
"name": "morel paddenstoel",
"plural_name": "morel paddenstoelen"
"name": "morel mushroom",
"plural_name": "morel mushrooms"
},
"field mushroom": {
"aliases": [],
"description": "",
"name": "veld paddestoel",
"plural_name": "veld paddenstoelen"
"name": "field mushroom",
"plural_name": "field mushrooms"
},
"king oyster mushroom": {
"aliases": [],
"description": "",
"name": "koning oesterzwam",
"plural_name": "koning oesterzwammen"
"name": "king oyster mushroom",
"plural_name": "king oyster mushrooms"
},
"shimeji mushroom": {
"aliases": [],
"description": "",
"name": "shimeji paddenstoel",
"plural_name": "shimeji paddenstoelen"
"name": "shimeji mushroom",
"plural_name": "shimeji mushrooms"
},
"straw mushroom": {
"aliases": [],
"description": "",
"name": "stro paddenstoel",
"plural_name": "stro paddenstoelen"
"name": "straw mushroom",
"plural_name": "straw mushrooms"
},
"dried chinese mushroom": {
"aliases": [],
"description": "",
"name": "gedroogde chinese paddenstoel",
"plural_name": "gedroogde chinese paddenstoelen"
"name": "dried chinese mushroom",
"plural_name": "dried chinese mushrooms"
},
"maitake": {
"aliases": [],
@@ -1347,79 +1347,79 @@
"trumpet mushroom": {
"aliases": [],
"description": "",
"name": "trompet paddenstoel",
"plural_name": "trompet paddenstoelen"
"name": "trumpet mushroom",
"plural_name": "trumpet mushrooms"
},
"white truffle": {
"aliases": [],
"description": "",
"name": "witte truffel",
"plural_name": "witte truffels"
"name": "white truffle",
"plural_name": "white truffles"
},
"white fungu": {
"aliases": [],
"description": "",
"name": "witte zwam",
"plural_name": "witte zwammen"
"name": "white fungu",
"plural_name": "white fungus"
},
"pioppini": {
"aliases": [],
"description": "",
"name": "pioppino-zwam",
"plural_name": "pioppino-zwammen"
"name": "pioppini",
"plural_name": "pioppinis"
},
"snow fungu": {
"aliases": [],
"description": "",
"name": "sneeuwzwam",
"plural_name": "sneeuwzwammen"
"name": "snow fungu",
"plural_name": "snow fungus"
},
"white beech mushroom": {
"aliases": [],
"description": "",
"name": "witte beukenzwam",
"plural_name": "witte beukenzwammen"
"name": "white beech mushroom",
"plural_name": "white beech mushrooms"
},
"boletu": {
"aliases": [],
"description": "",
"name": "boleet",
"plural_name": "boleten"
"name": "boletu",
"plural_name": "boletus"
},
"huitlacoche": {
"aliases": [],
"description": "",
"name": "maïsbrand",
"plural_name": "maïsbrand"
"name": "huitlacoche",
"plural_name": "huitlacoches"
},
"matsutake": {
"aliases": [],
"description": "",
"name": "pijnboomzwam",
"plural_name": "pijnboomzwammen"
"name": "matsutake",
"plural_name": "matsutakes"
},
"nameko": {
"aliases": [],
"description": "",
"name": "bundelzwam",
"plural_name": "bundelzwammen"
"name": "nameko",
"plural_name": "namekoes"
},
"djon djon mushroom": {
"aliases": [],
"description": "",
"name": "zwarte trompetzwam",
"plural_name": "zwarte trompetzwammen"
"name": "djon djon mushroom",
"plural_name": "djon djon mushrooms"
},
"mixed asian mushroom": {
"aliases": [],
"description": "",
"name": "gemengde aziatische paddenstoelen",
"plural_name": "gemengde aziatische paddenstoelen"
"name": "mixed asian mushroom",
"plural_name": "mixed asian mushrooms"
},
"puffball": {
"aliases": [],
"description": "",
"name": "bovist",
"name": "puffball",
"plural_name": "puffballs"
},
"honey fungu": {
@@ -1453,98 +1453,98 @@
"strawberry": {
"aliases": [],
"description": "",
"name": "aardbei",
"plural_name": "aardbeien"
"name": "strawberry",
"plural_name": "strawberries"
},
"blueberry": {
"aliases": [],
"description": "",
"name": "bosbes",
"plural_name": "bosbessen"
"name": "blueberry",
"plural_name": "blueberries"
},
"raspberry": {
"aliases": [],
"description": "",
"name": "framboos",
"plural_name": "frambozen"
"name": "raspberry",
"plural_name": "raspberries"
},
"cranberry": {
"aliases": [],
"description": "",
"name": "veenbes",
"plural_name": "veenbessen"
"name": "cranberry",
"plural_name": "cranberries"
},
"cherry": {
"aliases": [],
"description": "",
"name": "kers",
"plural_name": "kersen"
"name": "cherry",
"plural_name": "cherries"
},
"blackberry": {
"aliases": [],
"description": "",
"name": "braam",
"plural_name": "bramen"
"name": "blackberry",
"plural_name": "blackberries"
},
"berry mix": {
"aliases": [],
"description": "",
"name": "gemengde bessen",
"plural_name": "gemengde bessen"
"name": "berry mix",
"plural_name": "berry mixes"
},
"maraschino cherry": {
"aliases": [],
"description": "",
"name": "maraschino kers",
"plural_name": "maraschino kersen"
"name": "maraschino cherry",
"plural_name": "maraschino cherries"
},
"dried cherry": {
"aliases": [],
"description": "",
"name": "gedroogde kers",
"plural_name": "gedroogde kersen"
"name": "dried cherry",
"plural_name": "dried cherries"
},
"juniper berry": {
"aliases": [],
"description": "",
"name": "jeneverbes",
"plural_name": "jeneverbessen"
"name": "juniper berry",
"plural_name": "juniper berries"
},
"sour cherry": {
"aliases": [],
"description": "",
"name": "zure kers",
"plural_name": "zure kersen"
"name": "sour cherry",
"plural_name": "sour cherries"
},
"goji berry": {
"aliases": [],
"description": "",
"name": "goji bes",
"plural_name": "goji bessen"
"name": "goji berry",
"plural_name": "goji berries"
},
"dried blueberry": {
"aliases": [],
"description": "",
"name": "gedroogde bosbes",
"plural_name": "gedroogde bosbessen"
"name": "dried blueberry",
"plural_name": "dried blueberries"
},
"freeze-dried strawberry": {
"aliases": [],
"description": "",
"name": "gevriesdroogde aardbei",
"plural_name": "gevriesdroogde aardbeien"
"name": "freeze-dried strawberry",
"plural_name": "freeze-dried strawberries"
},
"gooseberry": {
"aliases": [],
"description": "",
"name": "kruisbes",
"plural_name": "kruisbessen"
"name": "gooseberry",
"plural_name": "gooseberries"
},
"freeze-dried raspberry": {
"aliases": [],
"description": "",
"name": "gevriesdroogde framboos",
"plural_name": "gevriesdroogde frambozen"
"name": "freeze-dried raspberry",
"plural_name": "freeze-dried raspberries"
},
"lingonberry": {
"aliases": [],
@@ -1640,19 +1640,19 @@
"aliases": [],
"description": "",
"name": "hawthorn",
"plural_name": "meidoorns"
"plural_name": "hawthorns"
},
"boysenberry": {
"aliases": [],
"description": "",
"name": "wilde braam",
"plural_name": "wilde bramen"
"name": "boysenberry",
"plural_name": "boysenberries"
},
"cloudberry": {
"aliases": [],
"description": "",
"name": "kruipbraam",
"plural_name": "kruipbramen"
"name": "cloudberry",
"plural_name": "cloudberries"
},
"freeze-dried berry": {
"aliases": [],
@@ -1715,20 +1715,20 @@
"walnut": {
"aliases": [],
"description": "",
"name": "walnoot",
"plural_name": "walnoten"
"name": "walnut",
"plural_name": "walnuts"
},
"pecan": {
"aliases": [],
"description": "",
"name": "pecannoot",
"plural_name": "pecannoten"
"name": "pecan",
"plural_name": "pecans"
},
"almond": {
"aliases": [],
"description": "",
"name": "amandel",
"plural_name": "amandelen"
"name": "almond",
"plural_name": "almonds"
},
"sesame seed": {
"aliases": [],
@@ -1739,8 +1739,8 @@
"cashew": {
"aliases": [],
"description": "",
"name": "cashewnoot",
"plural_name": "cashewnoten"
"name": "cashew",
"plural_name": "cashews"
},
"pine nut": {
"aliases": [],
@@ -1751,14 +1751,14 @@
"pistachio": {
"aliases": [],
"description": "",
"name": "pistachenoot",
"plural_name": "pistachenoten"
"name": "pistachio",
"plural_name": "pistachios"
},
"peanut": {
"aliases": [],
"description": "",
"name": "pinda",
"plural_name": "pinda's"
"name": "peanut",
"plural_name": "peanuts"
},
"chia": {
"aliases": [],
@@ -1811,8 +1811,8 @@
"roasted peanut": {
"aliases": [],
"description": "",
"name": "geroosterde pinda",
"plural_name": "geroosterde pinda's"
"name": "roasted peanut",
"plural_name": "roasted peanuts"
},
"chopped nut": {
"aliases": [],