Merge branch 'mealie-next' into feat/announcements

This commit is contained in:
Michael Genson
2026-04-08 15:49:46 +00:00
598 changed files with 17278 additions and 17950 deletions

View File

@@ -17,12 +17,12 @@ jobs:
steps: steps:
- name: Checkout 🛎 - name: Checkout 🛎
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
ref: ${{ inputs.ref || github.sha }} ref: ${{ inputs.ref || github.sha }}
- name: Setup node env 🏗 - name: Setup node env 🏗
uses: actions/setup-node@v4.0.0 uses: actions/setup-node@v6
with: with:
node-version: 22 node-version: 22
check-latest: true check-latest: true
@@ -32,7 +32,7 @@ jobs:
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- name: Cache node_modules 📦 - name: Cache node_modules 📦
uses: actions/cache@v4 uses: actions/cache@v5
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with: with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
@@ -49,7 +49,7 @@ jobs:
working-directory: "frontend" working-directory: "frontend"
- name: Archive built frontend - name: Archive built frontend
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: frontend-dist name: frontend-dist
path: frontend/dist path: frontend/dist
@@ -68,12 +68,12 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Check out repository - name: Check out repository
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
ref: ${{ inputs.ref || github.sha }} ref: ${{ inputs.ref || github.sha }}
- name: Set up python - name: Set up python
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: "3.12" python-version: "3.12"
@@ -81,7 +81,7 @@ jobs:
run: pip install uv run: pip install uv
- name: Retrieve built frontend - name: Retrieve built frontend
uses: actions/download-artifact@v4 uses: actions/download-artifact@v6
with: with:
name: frontend-dist name: frontend-dist
path: mealie/frontend path: mealie/frontend
@@ -97,7 +97,7 @@ jobs:
task py:package task py:package
- name: Archive built package - name: Archive built package
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: backend-dist name: backend-dist
path: dist path: dist

View File

@@ -44,11 +44,11 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v6
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v4
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -62,7 +62,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v3 uses: github/codeql-action/autobuild@v4
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -75,6 +75,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh # ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@v4
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

View File

@@ -21,7 +21,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@v4 uses: astral-sh/setup-uv@v4

View File

@@ -10,21 +10,21 @@ jobs:
run: run:
working-directory: ./tests/e2e working-directory: ./tests/e2e
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-node@v4 - uses: actions/setup-node@v6
with: with:
node-version: 22 node-version: 22
cache: 'yarn' cache: 'yarn'
cache-dependency-path: ./tests/e2e/yarn.lock cache-dependency-path: ./tests/e2e/yarn.lock
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Retrieve Python package - name: Retrieve Python package
uses: actions/download-artifact@v4 uses: actions/download-artifact@v6
with: with:
name: backend-dist name: backend-dist
path: dist path: dist
- name: Build Image - name: Build Image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v7
with: with:
file: ./docker/Dockerfile file: ./docker/Dockerfile
context: . context: .

View File

@@ -23,12 +23,12 @@ jobs:
private-key: ${{ secrets.COMMIT_BOT_APP_PRIVATE_KEY }} private-key: ${{ secrets.COMMIT_BOT_APP_PRIVATE_KEY }}
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
token: ${{ steps.app-token.outputs.token }} token: ${{ steps.app-token.outputs.token }}
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: "3.12" python-version: "3.12"
@@ -37,7 +37,7 @@ jobs:
- name: Load cached venv - name: Load cached venv
id: cached-python-dependencies id: cached-python-dependencies
uses: actions/cache@v4 uses: actions/cache@v5
with: with:
path: .venv path: .venv
key: venv-${{ runner.os }}-${{ hashFiles('**/uv.lock') }} key: venv-${{ runner.os }}-${{ hashFiles('**/uv.lock') }}

View File

@@ -11,7 +11,7 @@ jobs:
fail-fast: true fail-fast: true
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Build Dockerfile - name: Build Dockerfile
run: | run: |
@@ -28,6 +28,6 @@ jobs:
TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db,public.ecr.aws/aquasecurity/trivy-db TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db,public.ecr.aws/aquasecurity/trivy-db
- name: Upload Trivy scan results to GitHub Security tab - name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3 uses: github/codeql-action/upload-sarif@v4
with: with:
sarif_file: "trivy-results.sarif" sarif_file: "trivy-results.sarif"

View File

@@ -23,19 +23,19 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
ref: ${{ inputs.ref || github.sha }} ref: ${{ inputs.ref || github.sha }}
- name: Log in to the Container registry (ghcr.io) - name: Log in to the Container registry (ghcr.io)
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to the Container registry (dockerhub) - name: Log in to the Container registry (dockerhub)
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -44,7 +44,7 @@ jobs:
- name: Generate Docker metadata - name: Generate Docker metadata
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: | images: |
hkotel/mealie hkotel/mealie
@@ -52,9 +52,10 @@ jobs:
# Overwrite the image.version label with our tag # Overwrite the image.version label with our tag
labels: | labels: |
org.opencontainers.image.version=${{ inputs.tag }} org.opencontainers.image.version=${{ inputs.tag }}
org.opencontainers.image.revision=${{ inputs.ref || github.sha }}
- name: Retrieve Python package - name: Retrieve Python package
uses: actions/download-artifact@v4 uses: actions/download-artifact@v6
with: with:
name: backend-dist name: backend-dist
path: dist path: dist

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
# https://github.com/amannn/action-semantic-pull-request # https://github.com/amannn/action-semantic-pull-request
- uses: amannn/action-semantic-pull-request@v5 - uses: amannn/action-semantic-pull-request@v6
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:

View File

@@ -5,26 +5,28 @@ on:
push: push:
branches: branches:
- mealie-next - mealie-next
# pull_request event is required for autolabeler
pull_request:
types: [opened, labeled, unlabeled, reopened, synchronize]
# pull_request_target event is required for autolabeler to support PRs from forks # pull_request_target event is required for autolabeler to support PRs from forks
pull_request_target: pull_request_target:
types: [opened, labeled, unlabeled, reopened, synchronize] types: [opened, labeled, unlabeled, reopened, synchronize]
workflow_dispatch: workflow_dispatch:
jobs: jobs:
update_release_draft: draft_release:
permissions: if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
# write permission is required to create a github release
contents: write
# write permission is required for autolabeler
# otherwise, read permission is required at least
pull-requests: write
name: ✏️ Draft release
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: write
steps: steps:
- name: 🚀 Run Release Drafter - uses: release-drafter/release-drafter@v7
uses: release-drafter/release-drafter@v6.0.0 env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
auto_label:
if: github.event_name == 'pull_request_target'
permissions:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter/autolabeler@v7
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -21,7 +21,7 @@ jobs:
private-key: ${{ secrets.COMMIT_BOT_APP_PRIVATE_KEY }} private-key: ${{ secrets.COMMIT_BOT_APP_PRIVATE_KEY }}
- name: Checkout 🛎 - name: Checkout 🛎
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
token: ${{ steps.app-token.outputs.token }} token: ${{ steps.app-token.outputs.token }}
@@ -124,7 +124,7 @@ jobs:
private-key: ${{ secrets.COMMIT_BOT_APP_PRIVATE_KEY }} private-key: ${{ secrets.COMMIT_BOT_APP_PRIVATE_KEY }}
- name: Checkout 🛎 - name: Checkout 🛎
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
token: ${{ steps.app-token.outputs.token }} token: ${{ steps.app-token.outputs.token }}
fetch-depth: 0 fetch-depth: 0

View File

@@ -13,10 +13,10 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Checkout 🛎 - name: Checkout 🛎
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: "3.12" python-version: "3.12"
@@ -25,7 +25,7 @@ jobs:
run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
- name: Cache - name: Cache
uses: actions/cache@v4 uses: actions/cache@v5
with: with:
path: | path: |
~/.cache/pre-commit ~/.cache/pre-commit

View File

@@ -46,12 +46,12 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Check out repository - name: Check out repository
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
ref: ${{ inputs.ref || github.sha }} ref: ${{ inputs.ref || github.sha }}
- name: Set up python - name: Set up python
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: "3.12" python-version: "3.12"
@@ -60,7 +60,7 @@ jobs:
- name: Load cached venv - name: Load cached venv
id: cached-python-dependencies id: cached-python-dependencies
uses: actions/cache@v4 uses: actions/cache@v5
with: with:
path: .venv path: .venv
key: venv-${{ runner.os }}-${{ hashFiles('**/uv.lock') }} key: venv-${{ runner.os }}-${{ hashFiles('**/uv.lock') }}

View File

@@ -13,12 +13,12 @@ jobs:
steps: steps:
- name: Checkout 🛎 - name: Checkout 🛎
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
ref: ${{ inputs.ref || github.sha }} ref: ${{ inputs.ref || github.sha }}
- name: Setup node env 🏗 - name: Setup node env 🏗
uses: actions/setup-node@v4.0.0 uses: actions/setup-node@v6
with: with:
node-version: 22 node-version: 22
check-latest: true check-latest: true
@@ -28,7 +28,7 @@ jobs:
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- name: Cache node_modules 📦 - name: Cache node_modules 📦
uses: actions/cache@v4 uses: actions/cache@v5
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with: with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View File

@@ -17,6 +17,8 @@
"source.fixAll.eslint": "explicit", "source.fixAll.eslint": "explicit",
"source.organizeImports": "never" "source.organizeImports": "never"
}, },
"editor.insertSpaces": true,
"editor.tabSize": 2,
"editor.formatOnSave": true, "editor.formatOnSave": true,
"eslint.useFlatConfig": true, "eslint.useFlatConfig": true,
"eslint.workingDirectories": [ "eslint.workingDirectories": [
@@ -30,6 +32,7 @@
"**/.svn": true, "**/.svn": true,
"**/CVS": true "**/CVS": true
}, },
"files.insertFinalNewline": true,
"i18n-ally.enabledFrameworks": [ "i18n-ally.enabledFrameworks": [
"vue" "vue"
], ],
@@ -67,6 +70,7 @@
}, },
"[python]": { "[python]": {
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.defaultFormatter": "charliermarsh.ruff" "editor.defaultFormatter": "charliermarsh.ruff",
"editor.tabSize": 4
} }
} }

View File

@@ -1,7 +1,7 @@
############################################### ###############################################
# Frontend Build # Frontend Build
############################################### ###############################################
FROM node:24@sha256:bb20cf73b3ad7212834ec48e2174cdcb5775f6550510a5336b842ae32741ce6c \ FROM node:24@sha256:80fc934952c8f1b2b4d39907af7211f8a9fff1a4c2cf673fb49099292c251cec \
AS frontend-builder AS frontend-builder
WORKDIR /frontend WORKDIR /frontend

View File

@@ -160,13 +160,14 @@
</v-row> </v-row>
</div> </div>
<v-card v-intersect="infiniteScroll" /> <v-card v-intersect="infiniteScroll" />
<v-fade-transition>
<AppLoader
v-if="loading"
:loading="loading"
/>
</v-fade-transition>
</div> </div>
<v-fade-transition>
<AppLoader
v-if="loading"
:loading="loading"
/>
</v-fade-transition>
<AppScrollToTop />
</div> </div>
</template> </template>
@@ -243,6 +244,7 @@ const ready = ref(false);
const loading = ref(false); const loading = ref(false);
const { fetchMore, getRandom } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value); const { fetchMore, getRandom } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value);
const { savePosition, getSavedPage, restorePosition } = useScrollPosition();
const router = useRouter(); const router = useRouter();
const queryFilter = computed(() => { const queryFilter = computed(() => {
@@ -283,8 +285,29 @@ async function fetchRecipes(pageCount = 1) {
} }
onMounted(async () => { onMounted(async () => {
await initRecipes(); loading.value = true;
ready.value = true; const savedPage = getSavedPage(route.path);
if (savedPage && savedPage > 2) {
page.value = 1;
hasMore.value = true;
const newRecipes = await fetchRecipes(savedPage);
if (newRecipes.length < perPage * savedPage) {
hasMore.value = false;
}
page.value = savedPage;
emit(REPLACE_RECIPES_EVENT, newRecipes);
ready.value = true;
restorePosition(route.path);
}
else {
await initRecipes();
ready.value = true;
if (savedPage) {
restorePosition(route.path);
}
}
loading.value = false;
}); });
let lastQuery: string | undefined = JSON.stringify(props.query); let lastQuery: string | undefined = JSON.stringify(props.query);
@@ -337,6 +360,8 @@ const infiniteScroll = useThrottleFn(async () => {
emit(APPEND_RECIPES_EVENT, newRecipes); emit(APPEND_RECIPES_EVENT, newRecipes);
} }
savePosition(route.path, page.value);
loading.value = false; loading.value = false;
}, 500); }, 500);

View File

@@ -1,91 +1,60 @@
<template> <template>
<div class="text-center"> <div class="text-center">
<v-dialog <BaseButton @click="dialog = true">
{{ $t("new-recipe.bulk-add") }}
</BaseButton>
<BaseDialog
v-model="dialog" v-model="dialog"
width="800" width="800"
:title="$t('new-recipe.bulk-add')"
:icon="$globals.icons.createAlt"
:submit-text="$t('general.add')"
:disable-submit-on-enter="true"
can-submit
@submit="save"
> >
<template #activator="{ props: activatorProps }"> <v-card-text>
<BaseButton <v-textarea
v-bind="activatorProps" v-model="inputText"
@click="inputText = inputTextProp" variant="outlined"
> rows="12"
{{ $t("new-recipe.bulk-add") }} hide-details
</BaseButton> autofocus
</template> :placeholder="$t('new-recipe.paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list')"
/>
<v-card> <v-divider />
<v-app-bar <v-list lines="two">
density="compact"
dark
color="primary"
class="mb-2 position-relative left-0 top-0 w-100"
>
<v-icon
size="large"
start
>
{{ $globals.icons.createAlt }}
</v-icon>
<v-toolbar-title class="headline">
{{ $t("new-recipe.bulk-add") }}
</v-toolbar-title>
<v-spacer />
</v-app-bar>
<v-card-text>
<v-textarea
v-model="inputText"
variant="outlined"
rows="12"
hide-details
:placeholder="$t('new-recipe.paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list')"
/>
<v-divider />
<template <template
v-for="(util) in utilities" v-for="(util) in utilities"
:key="util.id" :key="util.id"
> >
<v-list-item <v-list-item
density="compact" class="px-0"
class="py-1"
> >
<v-list-item-title> <template #prepend>
<v-list-item-subtitle class="wrap-word"> <v-avatar>
{{ util.description }} <v-btn
</v-list-item-subtitle> icon
variant="tonal"
base-color="info"
:title="$t('general.run')"
@click="util.action"
>
<v-icon>
{{ $globals.icons.play }}
</v-icon>
</v-btn>
</v-avatar>
</template>
<v-list-item-title class="text-pre-wrap">
{{ util.description }}
</v-list-item-title> </v-list-item-title>
<BaseButton
size="small"
color="info"
@click="util.action"
>
<template #icon>
{{ $globals.icons.robot }}
</template>
{{ $t("general.run") }}
</BaseButton>
</v-list-item> </v-list-item>
<v-divider class="mx-2" />
</template> </template>
</v-card-text> </v-list>
</v-card-text>
<v-divider /> </BaseDialog>
<v-card-actions>
<BaseButton
cancel
@click="dialog = false"
/>
<v-spacer />
<BaseButton
save
color="success"
@click="save"
/>
</v-card-actions>
</v-card>
</v-dialog>
</div> </div>
</template> </template>

View File

@@ -1,62 +1,30 @@
<template> <template>
<div> <div>
<v-dialog <BaseDialog
v-model="dialog" v-model="dialog"
width="500" width="500"
:title="properties.title"
:icon="properties.icon"
can-submit
:submit-disabled="!name"
@submit="select"
> >
<v-card> <v-form>
<v-app-bar <v-card-text>
density="compact" <v-text-field
dark v-model="name"
color="primary mb-2 position-relative left-0 top-0 w-100 pl-3" :label="properties.label"
> :rules="[rules.required]"
<v-icon autofocus
size="large" />
start <v-checkbox
class="mt-1" v-if="itemType === Organizer.Tool"
> v-model="onHand"
{{ itemType === Organizer.Tool ? $globals.icons.potSteam :label="$t('tool.on-hand')"
: itemType === Organizer.Category ? $globals.icons.categories />
: $globals.icons.tags }} </v-card-text>
</v-icon> </v-form>
</BaseDialog>
<v-toolbar-title class="headline">
{{ properties.title }}
</v-toolbar-title>
<v-spacer />
</v-app-bar>
<v-card-title />
<v-form @submit.prevent="select">
<v-card-text>
<v-text-field
v-model="name"
density="compact"
:label="properties.label"
:rules="[rules.required]"
autofocus
/>
<v-checkbox
v-if="itemType === Organizer.Tool"
v-model="onHand"
:label="$t('tool.on-hand')"
/>
</v-card-text>
<v-card-actions>
<BaseButton
cancel
@click="dialog = false"
/>
<v-spacer />
<BaseButton
type="submit"
create
:disabled="!name"
/>
</v-card-actions>
</v-form>
</v-card>
</v-dialog>
</div> </div>
</template> </template>
@@ -65,6 +33,8 @@ import { useUserApi } from "~/composables/api";
import { useCategoryStore, useTagStore, useToolStore } from "~/composables/store"; import { useCategoryStore, useTagStore, useToolStore } from "~/composables/store";
import { type RecipeOrganizer, Organizer } from "~/lib/api/types/non-generated"; import { type RecipeOrganizer, Organizer } from "~/lib/api/types/non-generated";
const { $globals } = useNuxtApp();
const CREATED_ITEM_EVENT = "created-item"; const CREATED_ITEM_EVENT = "created-item";
interface Props { interface Props {
@@ -115,18 +85,21 @@ const properties = computed(() => {
return { return {
title: i18n.t("tag.create-a-tag"), title: i18n.t("tag.create-a-tag"),
label: i18n.t("tag.tag-name"), label: i18n.t("tag.tag-name"),
icon: $globals.icons.tags,
api: userApi.tags, api: userApi.tags,
}; };
case Organizer.Tool: case Organizer.Tool:
return { return {
title: i18n.t("tool.create-a-tool"), title: i18n.t("tool.create-a-tool"),
label: i18n.t("tool.tool-name"), label: i18n.t("tool.tool-name"),
icon: $globals.icons.potSteam,
api: userApi.tools, api: userApi.tools,
}; };
default: default:
return { return {
title: i18n.t("category.create-a-category"), title: i18n.t("category.create-a-category"),
label: i18n.t("category.category-name"), label: i18n.t("category.category-name"),
icon: $globals.icons.categories,
api: userApi.categories, api: userApi.categories,
}; };
} }
@@ -139,12 +112,9 @@ const rules = {
async function select() { async function select() {
if (store) { if (store) {
// @ts-expect-error the same state is used for different organizer types, which have different requirements // @ts-expect-error the same state is used for different organizer types, which have different requirements
await store.actions.createOne({ name: name.value, onHand: onHand.value }); const newItem = await store.actions.createOne({ name: name.value, onHand: onHand.value });
emit(CREATED_ITEM_EVENT, newItem);
} }
const newItem = store.store.value.find(item => item.name === name.value);
emit(CREATED_ITEM_EVENT, newItem);
dialog.value = false; dialog.value = false;
} }
</script> </script>

View File

@@ -26,6 +26,7 @@
v-if="updateTarget" v-if="updateTarget"
v-model="dialogs.update" v-model="dialogs.update"
:title="$t('general.update')" :title="$t('general.update')"
:icon="$globals.icons.edit"
can-confirm can-confirm
@confirm="updateOne()" @confirm="updateOne()"
> >

View File

@@ -82,7 +82,7 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script setup lang="ts">
import { useUserApi } from "~/composables/api"; import { useUserApi } from "~/composables/api";
import type { Recipe } from "~/lib/api/types/recipe"; import type { Recipe } from "~/lib/api/types/recipe";
import UserAvatar from "~/components/Domain/User/UserAvatar.vue"; import UserAvatar from "~/components/Domain/User/UserAvatar.vue";

View File

@@ -1,117 +1,101 @@
<template> <template>
<section @keyup.ctrl.z="undoMerge"> <section @keyup.ctrl.z="undoMerge">
<!-- Ingredient Link Editor --> <!-- Ingredient Link Editor -->
<v-dialog <BaseDialog
v-if="dialog"
v-model="dialog" v-model="dialog"
width="600" :title="$t('recipe.ingredient-linker')"
:icon="$globals.icons.link"
width="100%"
max-width="600px"
max-height="40%"
> >
<v-card :ripple="false"> <v-card-text class="pt-4">
<v-sheet <p>
color="primary" {{ activeText }}
class="mt-n1 mb-3 pa-3 d-flex align-center" </p>
style="border-radius: 6px; width: 100%;" <v-divider class="my-4" />
> <template v-if="Object.keys(groupedUnusedIngredients).length > 0">
<v-icon <h4 class="ml-1">
size="large" {{ $t("recipe.unlinked") }}
start </h4>
> <template v-for="(ingredients, title) in groupedUnusedIngredients" :key="title">
{{ $globals.icons.link }} <h4 v-if="title" class="py-3 ml-1 pl-4">
</v-icon> {{ title }}
<v-toolbar-title class="headline">
{{ $t("recipe.ingredient-linker") }}
</v-toolbar-title>
<v-spacer />
</v-sheet>
<v-card-text class="pt-4">
<p>
{{ activeText }}
</p>
<v-divider class="mb-4" />
<template v-if="Object.keys(groupedUnusedIngredients).length > 0">
<h4 class="py-3 ml-1">
{{ $t("recipe.unlinked") }}
</h4> </h4>
<template v-for="(ingredients, title) in groupedUnusedIngredients" :key="title"> <v-checkbox-btn
<h4 v-if="title" class="py-3 ml-1 pl-4"> v-for="ing in ingredients"
{{ title }} :key="ing.referenceId"
</h4> v-model="activeRefs"
<v-checkbox-btn :value="ing.referenceId"
v-for="ing in ingredients" class="ml-4"
:key="ing.referenceId" >
v-model="activeRefs" <template #label>
:value="ing.referenceId" <RecipeIngredientHtml :ingredient="ing" :scale="scale" />
class="ml-4" </template>
> </v-checkbox-btn>
<template #label>
<RecipeIngredientHtml :ingredient="ing" :scale="scale" />
</template>
</v-checkbox-btn>
</template>
</template> </template>
</template>
<template v-if="Object.keys(groupedUsedIngredients).length > 0"> <template v-if="Object.keys(groupedUsedIngredients).length > 0">
<h4 class="py-3 ml-1"> <h4 class="py-3 ml-1">
{{ $t("recipe.linked-to-other-step") }} {{ $t("recipe.linked-to-other-step") }}
</h4>
<template v-for="(ingredients, title) in groupedUsedIngredients" :key="title">
<h4 v-if="title" class="py-3 ml-1 pl-4">
{{ title }}
</h4> </h4>
<template v-for="(ingredients, title) in groupedUsedIngredients" :key="title"> <v-checkbox-btn
<h4 v-if="title" class="py-3 ml-1 pl-4"> v-for="ing in ingredients"
{{ title }} :key="ing.referenceId"
</h4> v-model="activeRefs"
<v-checkbox-btn :value="ing.referenceId"
v-for="ing in ingredients" class="ml-4"
:key="ing.referenceId" >
v-model="activeRefs" <template #label>
:value="ing.referenceId" <RecipeIngredientHtml :ingredient="ing" :scale="scale" />
class="ml-4" </template>
> </v-checkbox-btn>
<template #label>
<RecipeIngredientHtml :ingredient="ing" :scale="scale" />
</template>
</v-checkbox-btn>
</template>
</template> </template>
</v-card-text> </template>
</v-card-text>
<v-divider /> <v-divider />
<v-card-actions> <template #card-actions>
<BaseButton
cancel
@click="dialog = false"
/>
<v-spacer />
<div class="d-flex flex-wrap justify-end">
<BaseButton <BaseButton
cancel class="my-1"
@click="dialog = false" color="info"
@click="autoSetReferences"
>
<template #icon>
{{ $globals.icons.robot }}
</template>
{{ $t("recipe.auto") }}
</BaseButton>
<BaseButton
class="ml-2 my-1"
save
@click="setIngredientIds"
/> />
<v-spacer /> <BaseButton
<div class="d-flex flex-wrap justify-end"> v-if="availableNextStep"
<BaseButton class="ml-2 my-1"
class="my-1" @click="saveAndOpenNextLinkIngredients"
color="info" >
@click="autoSetReferences" <template #icon>
> {{ $globals.icons.forward }}
<template #icon> </template>
{{ $globals.icons.robot }} {{ $t("recipe.nextStep") }}
</template> </BaseButton>
{{ $t("recipe.auto") }} </div>
</BaseButton> </template>
<BaseButton </BaseDialog>
class="ml-2 my-1"
save
@click="setIngredientIds"
/>
<BaseButton
v-if="availableNextStep"
class="ml-2 my-1"
@click="saveAndOpenNextLinkIngredients"
>
<template #icon>
{{ $globals.icons.forward }}
</template>
{{ $t("recipe.nextStep") }}
</BaseButton>
</div>
</v-card-actions>
</v-card>
</v-dialog>
<div class="d-flex justify-space-between justify-start"> <div class="d-flex justify-space-between justify-start">
<h2 <h2
@@ -851,6 +835,10 @@ function openImageUpload(index: number) {
font-size: 1.5rem; font-size: 1.5rem;
} }
.v-card-text {
font-size: 1rem;
}
.recipe-step-title { .recipe-step-title {
/* Multiline display */ /* Multiline display */
white-space: normal; white-space: normal;

View File

@@ -85,7 +85,7 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script setup lang="ts">
import { usePageState } from "~/composables/recipe-page/shared-state"; import { usePageState } from "~/composables/recipe-page/shared-state";
import type { NoUndefinedField } from "~/lib/api/types/non-generated"; import type { NoUndefinedField } from "~/lib/api/types/non-generated";
import type { Recipe } from "~/lib/api/types/recipe"; import type { Recipe } from "~/lib/api/types/recipe";

View File

@@ -36,7 +36,7 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script setup lang="ts">
import RecipeSettingsSwitches from "./RecipeSettingsSwitches.vue"; import RecipeSettingsSwitches from "./RecipeSettingsSwitches.vue";
const value = defineModel<object>({ required: true }); const value = defineModel<object>({ required: true });

View File

@@ -15,8 +15,7 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script setup lang="ts">
import { defineModel, defineProps } from "vue";
import type { RecipeSettings } from "~/lib/api/types/recipe"; import type { RecipeSettings } from "~/lib/api/types/recipe";
import { useI18n } from "#imports"; import { useI18n } from "#imports";

View File

@@ -24,7 +24,7 @@
</v-btn> </v-btn>
<BaseDialog <BaseDialog
v-model="showTimeline" v-model="showTimeline"
:title="timelineAttrs.title" :title="$t('recipe.timeline')"
:icon="$globals.icons.timelineText" :icon="$globals.icons.timelineText"
width="70%" width="70%"
> >
@@ -53,8 +53,6 @@ const props = withDefaults(defineProps<Props>(), {
recipeName: "", recipeName: "",
}); });
const i18n = useI18n();
const { smAndDown } = useDisplay();
const showTimeline = ref(false); const showTimeline = ref(false);
function toggleTimeline() { function toggleTimeline() {
@@ -62,13 +60,7 @@ function toggleTimeline() {
} }
const timelineAttrs = computed(() => { const timelineAttrs = computed(() => {
let title = i18n.t("recipe.timeline");
if (smAndDown.value) {
title += ` ${props.recipeName}`;
}
return { return {
title,
queryFilter: `recipe.slug="${props.slug}"`, queryFilter: `recipe.slug="${props.slug}"`,
}; };
}); });

View File

@@ -72,7 +72,7 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script setup lang="ts">
import { useI18n, useNuxtApp } from "#imports"; import { useI18n, useNuxtApp } from "#imports";
import type { RecipeTimelineEventOut } from "~/lib/api/types/recipe"; import type { RecipeTimelineEventOut } from "~/lib/api/types/recipe";

View File

@@ -152,7 +152,7 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script setup lang="ts">
import { useOnline } from "@vueuse/core"; import { useOnline } from "@vueuse/core";
import RecipeIngredientListItem from "../Recipe/RecipeIngredientListItem.vue"; import RecipeIngredientListItem from "../Recipe/RecipeIngredientListItem.vue";
import ShoppingListItemEditor from "./ShoppingListItemEditor.vue"; import ShoppingListItemEditor from "./ShoppingListItemEditor.vue";

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