Compare commits
465 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b54cdf6425 | ||
|
|
02da2114f9 | ||
|
|
a67533a778 | ||
|
|
59ad834c12 | ||
|
|
315d5b370e | ||
|
|
130813ffe4 | ||
|
|
65ddb7c9e2 | ||
|
|
dbe29e15ae | ||
|
|
980b3c634b | ||
|
|
457d8c93ce | ||
|
|
23aad6358c | ||
|
|
7c896361f2 | ||
|
|
5b7f5738e3 | ||
|
|
5bfcb80c98 | ||
|
|
b1278b45e2 | ||
|
|
e7ae76ea48 | ||
|
|
e84e5e2910 | ||
|
|
5e6f5bc175 | ||
|
|
d577978f2f | ||
|
|
e30728e8e9 | ||
|
|
29368d9cc4 | ||
|
|
4776da7aea | ||
|
|
87518b1fbd | ||
|
|
d7deb5a3f8 | ||
|
|
445ec18bb4 | ||
|
|
3ecc289e1a | ||
|
|
805e1c2d7d | ||
|
|
58286013c9 | ||
|
|
1afdc400a8 | ||
|
|
f6d526741b | ||
|
|
c9b21f862e | ||
|
|
4ae7f6eca4 | ||
|
|
c9fdf862a3 | ||
|
|
cf97c2247c | ||
|
|
9c95c56f0a | ||
|
|
9f3aca5a3f | ||
|
|
dd87779476 | ||
|
|
2e7078e90b | ||
|
|
6b52b61604 | ||
|
|
41e2643755 | ||
|
|
170b4d338d | ||
|
|
ecd506c714 | ||
|
|
e6aadc4902 | ||
|
|
ae74e0d71c | ||
|
|
d6db8c23ce | ||
|
|
dcf7afa441 | ||
|
|
185c93100b | ||
|
|
cf68420976 | ||
|
|
dea3b756f1 | ||
|
|
825b19c634 | ||
|
|
8d59c35bc9 | ||
|
|
06ec8dd4f3 | ||
|
|
fc4ec3261f | ||
|
|
663716ca0f | ||
|
|
6c4ce585d6 | ||
|
|
0acf30db61 | ||
|
|
db9035f92c | ||
|
|
a475afd570 | ||
|
|
57ca357969 | ||
|
|
cac099eeb3 | ||
|
|
c0929634a9 | ||
|
|
52de8afe2d | ||
|
|
2809b87ab0 | ||
|
|
7aab87813c | ||
|
|
4a67fffccd | ||
|
|
12b7625d42 | ||
|
|
275e1dc85c | ||
|
|
3a8e814315 | ||
|
|
07ebd1e613 | ||
|
|
a626330139 | ||
|
|
5ac7645350 | ||
|
|
efc6064605 | ||
|
|
71a6f32665 | ||
|
|
d60c4c179b | ||
|
|
c5c8c59168 | ||
|
|
fca75c5c07 | ||
|
|
012142feec | ||
|
|
d532395d89 | ||
|
|
b25f9f2cdf | ||
|
|
5471e742f0 | ||
|
|
c5849b2a74 | ||
|
|
429b2adf98 | ||
|
|
df366cd82a | ||
|
|
0a4bb583ff | ||
|
|
dfb650c4b1 | ||
|
|
aa4527e5f7 | ||
|
|
ae8ea16dab | ||
|
|
d321c69244 | ||
|
|
478a4e5d73 | ||
|
|
44cd2fef1c | ||
|
|
0abe8b1921 | ||
|
|
4a13714177 | ||
|
|
ac3514f4c6 | ||
|
|
f9b71f4b4c | ||
|
|
7ca50b63f9 | ||
|
|
62adc920a9 | ||
|
|
eeda71e186 | ||
|
|
8e5ea1df5e | ||
|
|
0bf3aed287 | ||
|
|
74d6f58363 | ||
|
|
3d4405cd42 | ||
|
|
98c8694979 | ||
|
|
32812d6a6c | ||
|
|
1f8d7c0b21 | ||
|
|
8b88f6892c | ||
|
|
618c567392 | ||
|
|
fb44451c6f | ||
|
|
9e1edbacb6 | ||
|
|
f45d02299a | ||
|
|
7afd7b2334 | ||
|
|
0a28d36df9 | ||
|
|
2c1185e1d4 | ||
|
|
69bbf9fdcc | ||
|
|
38125fa362 | ||
|
|
5db7a735d7 | ||
|
|
4d2363ea22 | ||
|
|
994940f270 | ||
|
|
3adb324b25 | ||
|
|
fbfc5b31d6 | ||
|
|
2a016ecce9 | ||
|
|
782d4ec180 | ||
|
|
8c52448da2 | ||
|
|
7e194887f5 | ||
|
|
806a1b9392 | ||
|
|
d575a3b222 | ||
|
|
f61fdb8623 | ||
|
|
ea7005e822 | ||
|
|
a7775ea7ef | ||
|
|
ba4eddccd9 | ||
|
|
6fcda5e446 | ||
|
|
34d742963a | ||
|
|
59cd68d54a | ||
|
|
1e4dbe4e95 | ||
|
|
5d89d53a4a | ||
|
|
349ccbad6f | ||
|
|
5d68620382 | ||
|
|
cb06c8a877 | ||
|
|
7d57fdcd96 | ||
|
|
fa60d81e26 | ||
|
|
6c4294dc49 | ||
|
|
0a3542e97c | ||
|
|
83887e3c37 | ||
|
|
5fe29cdd93 | ||
|
|
f618c45767 | ||
|
|
e1a87b32d9 | ||
|
|
9e739c8b35 | ||
|
|
d6fab197e7 | ||
|
|
89a5326d3f | ||
|
|
f0b542c990 | ||
|
|
c1a3516b37 | ||
|
|
db467105b9 | ||
|
|
2b5372f693 | ||
|
|
1a2ff9540f | ||
|
|
28fdc8a9ac | ||
|
|
b0ce1483fe | ||
|
|
ad251b2449 | ||
|
|
0ebc2a746b | ||
|
|
9c6e3ebe5b | ||
|
|
1b404ee6d8 | ||
|
|
72052be92f | ||
|
|
f2e7deb5cb | ||
|
|
b2e0c51ead | ||
|
|
3d73e7498f | ||
|
|
690f595491 | ||
|
|
058d968833 | ||
|
|
da2adaa694 | ||
|
|
fb9be66f97 | ||
|
|
0a446928d7 | ||
|
|
dc01ff36dc | ||
|
|
2d90ae903b | ||
|
|
d324c6ac57 | ||
|
|
aebf229b86 | ||
|
|
3c76a82997 | ||
|
|
30ec65f43c | ||
|
|
2471c7b08e | ||
|
|
4be23ccffb | ||
|
|
99db24cdec | ||
|
|
719a33352a | ||
|
|
fe3bd95c85 | ||
|
|
8db08c21e5 | ||
|
|
a384e6716d | ||
|
|
e35b2e9fbf | ||
|
|
b48c2ab736 | ||
|
|
a12ee536d9 | ||
|
|
97d5439a4f | ||
|
|
c94a1d7c17 | ||
|
|
0ce05c781c | ||
|
|
0e0dfbf014 | ||
|
|
ae03e61bb9 | ||
|
|
f6167b1d81 | ||
|
|
df75cb4034 | ||
|
|
67313f8f03 | ||
|
|
39eab01885 | ||
|
|
9fb63a00fd | ||
|
|
f945cb8d2d | ||
|
|
7a107584c7 | ||
|
|
248459671e | ||
|
|
67e48c2fd1 | ||
|
|
368d25fa01 | ||
|
|
12b1d29413 | ||
|
|
dccc676b24 | ||
|
|
d9c1cf8bec | ||
|
|
0836c303d9 | ||
|
|
a43fd6b7fc | ||
|
|
e7ee189fbb | ||
|
|
b6708613b9 | ||
|
|
3317e061a8 | ||
|
|
0dc8584485 | ||
|
|
673ad6d42b | ||
|
|
1450d6fc4c | ||
|
|
b082242439 | ||
|
|
de69a3ca86 | ||
|
|
96e37b3ee1 | ||
|
|
cb2d8a9a50 | ||
|
|
19c5b7c7ab | ||
|
|
38a4215b35 | ||
|
|
77a05c754e | ||
|
|
2226d7cbf9 | ||
|
|
42a33cd993 | ||
|
|
d73817adad | ||
|
|
ce58da8e18 | ||
|
|
fe89981e78 | ||
|
|
520bc7154a | ||
|
|
a38dfc094e | ||
|
|
0a81579da1 | ||
|
|
f4e77f6837 | ||
|
|
c1a2c7d485 | ||
|
|
8127f48924 | ||
|
|
a0e7f85c32 | ||
|
|
b45ffd2046 | ||
|
|
1e04e9424f | ||
|
|
ef4f6245d5 | ||
|
|
7bd1c8ef14 | ||
|
|
d1bbfece9d | ||
|
|
94a85f9977 | ||
|
|
3d0adda405 | ||
|
|
47086da6b6 | ||
|
|
9ce71c911f | ||
|
|
2244c3a8b5 | ||
|
|
f0d0d0d463 | ||
|
|
dbec3e58f9 | ||
|
|
a5e56dc97f | ||
|
|
6d64418727 | ||
|
|
2b71174765 | ||
|
|
7f6de730a3 | ||
|
|
22cc19a085 | ||
|
|
b16fa49f16 | ||
|
|
90e373582b | ||
|
|
1b3cbb38ae | ||
|
|
ac5a63e32d | ||
|
|
d5d86488a0 | ||
|
|
3f9c46a763 | ||
|
|
04176f6927 | ||
|
|
2aa8c5810a | ||
|
|
a071a7d16b | ||
|
|
a14c1b48c6 | ||
|
|
43174dcebe | ||
|
|
3ee53977ec | ||
|
|
de1486c57f | ||
|
|
a12aba6b9d | ||
|
|
fb70bc76b3 | ||
|
|
e6351273e2 | ||
|
|
82dcfb5635 | ||
|
|
dddeed6359 | ||
|
|
5f4a36bbd8 | ||
|
|
f74610a0f7 | ||
|
|
166f2486a2 | ||
|
|
88a5209237 | ||
|
|
d954869dd7 | ||
|
|
9cf181b415 | ||
|
|
5a7dc14a48 | ||
|
|
d916c0a472 | ||
|
|
a3693d83a3 | ||
|
|
bb9620b67e | ||
|
|
3174216931 | ||
|
|
94342081f9 | ||
|
|
5d049d5696 | ||
|
|
36088f0db9 | ||
|
|
4c60febb9c | ||
|
|
0283185913 | ||
|
|
704d0a8392 | ||
|
|
f42114e966 | ||
|
|
d4de15ba1e | ||
|
|
e686fa671c | ||
|
|
12547feb4c | ||
|
|
7dbc031725 | ||
|
|
597e6c8e0f | ||
|
|
d5ba69d828 | ||
|
|
564f43085b | ||
|
|
a0b6cc3e62 | ||
|
|
c960c00cbe | ||
|
|
47b60e9ad5 | ||
|
|
026ca0364e | ||
|
|
70ce34d6c9 | ||
|
|
dff351a8eb | ||
|
|
13e7dfe920 | ||
|
|
abf4b7706f | ||
|
|
813a124250 | ||
|
|
8a3173094e | ||
|
|
0d16a2a943 | ||
|
|
2918a824e4 | ||
|
|
00e5e4384d | ||
|
|
634b0590ed | ||
|
|
52c58e1dc0 | ||
|
|
67b7fb007b | ||
|
|
7299c9ec9a | ||
|
|
292672601c | ||
|
|
fa3bbdcde1 | ||
|
|
7e519c6b5a | ||
|
|
57c11b23c4 | ||
|
|
ba60428b03 | ||
|
|
e48619bae6 | ||
|
|
3a0e4ff119 | ||
|
|
6d38960a5a | ||
|
|
9cc59e81d6 | ||
|
|
a04b6983e7 | ||
|
|
6beea06a41 | ||
|
|
e75b5f2f15 | ||
|
|
c9acc48bd6 | ||
|
|
f4df68a9e2 | ||
|
|
4cee8ea879 | ||
|
|
e7f5a4adff | ||
|
|
0301713214 | ||
|
|
5ef23e0330 | ||
|
|
9bf2e3fabd | ||
|
|
dcf50b9a00 | ||
|
|
073efd7a2f | ||
|
|
88529457bf | ||
|
|
890b5d93a7 | ||
|
|
95b7990f26 | ||
|
|
7947aa99ae | ||
|
|
c3f7ad8954 | ||
|
|
7dafa6c7fe | ||
|
|
8d2d571683 | ||
|
|
19e776a772 | ||
|
|
8df7848c96 | ||
|
|
6097440781 | ||
|
|
208608b32e | ||
|
|
02997cd36e | ||
|
|
e1cd2717d3 | ||
|
|
694511cb60 | ||
|
|
f0c89525f6 | ||
|
|
a05ede5e05 | ||
|
|
7e51cf0352 | ||
|
|
ce110c23e4 | ||
|
|
8247f21101 | ||
|
|
60f9a3be5c | ||
|
|
dddcb644bf | ||
|
|
84e981fd03 | ||
|
|
937464115e | ||
|
|
24aee11607 | ||
|
|
023c57dd61 | ||
|
|
14d8ff8754 | ||
|
|
e6f531c111 | ||
|
|
c0a4f624d1 | ||
|
|
a32dc4baa0 | ||
|
|
7c4690a7a9 | ||
|
|
b44487596d | ||
|
|
2cb4c21db3 | ||
|
|
2dcc765e86 | ||
|
|
a5ef18669b | ||
|
|
0800a8d00a | ||
|
|
4d49e307e3 | ||
|
|
20621a1950 | ||
|
|
5a0b8940f5 | ||
|
|
c27d20b5c9 | ||
|
|
5f24e87e84 | ||
|
|
72980b3472 | ||
|
|
cedccf046b | ||
|
|
63514ab41c | ||
|
|
f8e8a40ec1 | ||
|
|
65ce53fb17 | ||
|
|
1352ae81c4 | ||
|
|
5c3b1f2890 | ||
|
|
555e341b65 | ||
|
|
6690ef8cab | ||
|
|
bbd6e7ef92 | ||
|
|
54a954164a | ||
|
|
d20ee21076 | ||
|
|
ce7efd7505 | ||
|
|
04f0d33ca3 | ||
|
|
01649de1e7 | ||
|
|
3a739ba194 | ||
|
|
5e78aa6e29 | ||
|
|
a121fe9b55 | ||
|
|
d1c4a9b422 | ||
|
|
e0c72c5508 | ||
|
|
4df4b7e0b6 | ||
|
|
e8d7c0423f | ||
|
|
583151087e | ||
|
|
c4b493564b | ||
|
|
e1aba3373a | ||
|
|
ee65d7d67d | ||
|
|
9029bccf5b | ||
|
|
4fdf844485 | ||
|
|
d2b1c2c5af | ||
|
|
84bd738ba5 | ||
|
|
e4eb4d3e3b | ||
|
|
422c485832 | ||
|
|
865ab6f843 | ||
|
|
cb948a8289 | ||
|
|
8d38ef1fd7 | ||
|
|
2ae2475b8e | ||
|
|
92c8b196de | ||
|
|
e6ad0aad81 | ||
|
|
c6fbf8bce8 | ||
|
|
8db5f7cce3 | ||
|
|
5f40064e2f | ||
|
|
e0cb6e0624 | ||
|
|
8bc73de815 | ||
|
|
71e2091130 | ||
|
|
8e65a4c65a | ||
|
|
34df20da81 | ||
|
|
254b6ae118 | ||
|
|
a840cb0800 | ||
|
|
098c8194f5 | ||
|
|
1daf41f452 | ||
|
|
72696cac20 | ||
|
|
4d3ea5d231 | ||
|
|
721063d091 | ||
|
|
4ae5c52de9 | ||
|
|
58cb43e999 | ||
|
|
0b0c25d2f3 | ||
|
|
21161dbf2e | ||
|
|
f62feb8da2 | ||
|
|
2cdbe816a6 | ||
|
|
e32fddbc85 | ||
|
|
8c17a81c91 | ||
|
|
d2188508fc | ||
|
|
09dfca4f34 | ||
|
|
61289416a5 | ||
|
|
16d3dbef5d | ||
|
|
702907fc30 | ||
|
|
600c569ae8 | ||
|
|
96995a4168 | ||
|
|
3a9fd11344 | ||
|
|
62dffb622f | ||
|
|
26dfc54d23 | ||
|
|
5d08647196 | ||
|
|
b51cd5d1c2 | ||
|
|
4e66d5fb92 | ||
|
|
14497b9b5e | ||
|
|
f77649abc8 | ||
|
|
33870dc845 | ||
|
|
265313919c | ||
|
|
dd5d1b9cba | ||
|
|
e90f05d2dc | ||
|
|
677dc8f36a | ||
|
|
b13d66108d | ||
|
|
449eeb0d53 | ||
|
|
41204ca7f9 | ||
|
|
437f5c454f | ||
|
|
3a30b3216e | ||
|
|
408df286fd | ||
|
|
2cfc63b302 | ||
|
|
a8583c8e69 | ||
|
|
6e2c30aba5 | ||
|
|
9c01b72292 | ||
|
|
18a405808a | ||
|
|
01e20acce7 | ||
|
|
1ee29e9e45 | ||
|
|
8a2d640922 | ||
|
|
8170e66f4f | ||
|
|
e50788f685 | ||
|
|
1197aa3f37 |
@@ -14,6 +14,7 @@ RUN echo "export PROMPT_COMMAND='history -a'" >> /home/vscode/.bashrc \
|
|||||||
&& echo "export HISTFILE=~/commandhistory/.bash_history" >> /home/vscode/.bashrc \
|
&& echo "export HISTFILE=~/commandhistory/.bash_history" >> /home/vscode/.bashrc \
|
||||||
&& chown vscode:vscode -R /home/vscode/
|
&& chown vscode:vscode -R /home/vscode/
|
||||||
|
|
||||||
|
RUN npm install -g @go-task/cli
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED=1 \
|
ENV PYTHONUNBUFFERED=1 \
|
||||||
PYTHONDONTWRITEBYTECODE=1 \
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
@@ -37,4 +38,3 @@ RUN apt-get update \
|
|||||||
libwebp-dev \
|
libwebp-dev \
|
||||||
libsasl2-dev libldap2-dev libssl-dev \
|
libsasl2-dev libldap2-dev libssl-dev \
|
||||||
gnupg gnupg2 gnupg1
|
gnupg gnupg2 gnupg1
|
||||||
# && pip install -U --no-cache-dir pip
|
|
||||||
|
|||||||
@@ -46,10 +46,12 @@
|
|||||||
],
|
],
|
||||||
// Use 'onCreateCommand' to run commands at the end of container creation.
|
// Use 'onCreateCommand' to run commands at the end of container creation.
|
||||||
// Use 'postCreateCommand' to run commands after the container is created.
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
"onCreateCommand": "sudo chown -R vscode:vscode /workspaces/mealie/frontend/node_modules && make setup",
|
"onCreateCommand": "sudo chown -R vscode:vscode /workspaces/mealie/frontend/node_modules && task setup",
|
||||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||||
"remoteUser": "vscode",
|
"remoteUser": "vscode",
|
||||||
// "features": {
|
"features": {
|
||||||
// "git": "latest"
|
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
||||||
// }
|
"dockerDashComposeVersion": "v2"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ body:
|
|||||||
options:
|
options:
|
||||||
- label: I used the GitHub search to find a similar requests and didn't find it.
|
- label: I used the GitHub search to find a similar requests and didn't find it.
|
||||||
required: true
|
required: true
|
||||||
- label: Checked the [tasks tagged](https://github.com/hay-kot/mealie/issues?q=is%3Aissue+is%3Aopen+label%3Atask+) issues and verified my feature is not covered
|
- label: Checked the [tasks tagged](https://github.com/mealie-recipes/mealie/issues?q=is%3Aissue+is%3Aopen+label%3Atask+) issues and verified my feature is not covered
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: problem
|
id: problem
|
||||||
|
|||||||
13
.github/workflows/nightly.yml
vendored
@@ -4,6 +4,12 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- mealie-next
|
- mealie-next
|
||||||
|
paths-ignore:
|
||||||
|
- '*.md'
|
||||||
|
- '.devcontainer/**'
|
||||||
|
# I'm not excluding .github as changes in there might be to workflows etc
|
||||||
|
- '.vscode/**'
|
||||||
|
- 'docs/**'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: nightly-${{ github.ref }}
|
group: nightly-${{ github.ref }}
|
||||||
@@ -22,7 +28,13 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
# The id-token write permission is needed to connect to Depot.dev
|
||||||
|
# as part of the partial-builder.yml action. It needs to be declared
|
||||||
|
# in the parent action, as noted here:
|
||||||
|
# https://github.com/orgs/community/discussions/76409#discussioncomment-8131390
|
||||||
|
id-token: write
|
||||||
name: Build Tagged Release
|
name: Build Tagged Release
|
||||||
|
if: github.repository == 'mealie-recipes/mealie'
|
||||||
uses: ./.github/workflows/partial-builder.yml
|
uses: ./.github/workflows/partial-builder.yml
|
||||||
needs:
|
needs:
|
||||||
- frontend-tests
|
- frontend-tests
|
||||||
@@ -35,6 +47,7 @@ jobs:
|
|||||||
|
|
||||||
notify-discord:
|
notify-discord:
|
||||||
name: Notify Discord
|
name: Notify Discord
|
||||||
|
if: github.repository == 'mealie-recipes/mealie'
|
||||||
needs:
|
needs:
|
||||||
- build-release
|
- build-release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
14
.github/workflows/partial-backend.yml
vendored
@@ -35,6 +35,12 @@ jobs:
|
|||||||
|
|
||||||
# Steps
|
# Steps
|
||||||
steps:
|
steps:
|
||||||
|
- name: Install Task
|
||||||
|
uses: arduino/setup-task@v1
|
||||||
|
with:
|
||||||
|
version: 3.x
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
@@ -69,7 +75,7 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install libsasl2-dev libldap2-dev libssl-dev
|
sudo apt-get install libsasl2-dev libldap2-dev libssl-dev
|
||||||
poetry install
|
poetry install
|
||||||
poetry add "psycopg2-binary==2.8.6"
|
poetry add "psycopg2-binary==2.9.9"
|
||||||
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' || steps.cache-validate.outputs.cache-hit-success != 'true'
|
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' || steps.cache-validate.outputs.cache-hit-success != 'true'
|
||||||
|
|
||||||
- name: Formatting (Black)
|
- name: Formatting (Black)
|
||||||
@@ -78,11 +84,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Lint (Ruff)
|
- name: Lint (Ruff)
|
||||||
run: |
|
run: |
|
||||||
make backend-lint
|
task py:lint
|
||||||
|
|
||||||
- name: Mypy Typecheck
|
- name: Mypy Typecheck
|
||||||
run: |
|
run: |
|
||||||
make backend-typecheck
|
task py:mypy
|
||||||
|
|
||||||
- name: Pytest
|
- name: Pytest
|
||||||
env:
|
env:
|
||||||
@@ -101,4 +107,4 @@ jobs:
|
|||||||
LDAP_NAME_ATTRIBUTE: cn
|
LDAP_NAME_ATTRIBUTE: cn
|
||||||
LDAP_MAIL_ATTRIBUTE: mail
|
LDAP_MAIL_ATTRIBUTE: mail
|
||||||
run: |
|
run: |
|
||||||
make backend-test
|
task py:test
|
||||||
|
|||||||
16
.github/workflows/partial-builder.yml
vendored
@@ -35,19 +35,16 @@ jobs:
|
|||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Override __init__.py
|
- name: Override __init__.py
|
||||||
run: |
|
run: |
|
||||||
echo "__version__ = \"${{ inputs.tag }}\"" > ./mealie/__init__.py
|
echo "__version__ = \"${{ inputs.tag }}\"" > ./mealie/__init__.py
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- uses: depot/setup-action@v1
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
|
- name: Build and push Docker image, via Depot.dev
|
||||||
|
uses: depot/build-push-action@v1
|
||||||
with:
|
with:
|
||||||
|
project: srzjb6mhzm
|
||||||
file: ./docker/Dockerfile
|
file: ./docker/Dockerfile
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
@@ -58,6 +55,3 @@ jobs:
|
|||||||
${{ inputs.tags }}
|
${{ inputs.tags }}
|
||||||
build-args: |
|
build-args: |
|
||||||
COMMIT=${{ github.sha }}
|
COMMIT=${{ github.sha }}
|
||||||
# https://docs.docker.com/build/ci/github-actions/cache/#github-cache
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|||||||
37
.github/workflows/release.yml
vendored
@@ -17,6 +17,11 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
# The id-token write permission is needed to connect to Depot.dev
|
||||||
|
# as part of the partial-builder.yml action. It needs to be declared
|
||||||
|
# in the parent action, as noted here:
|
||||||
|
# https://github.com/orgs/community/discussions/76409#discussioncomment-8131390
|
||||||
|
id-token: write
|
||||||
name: Build Tagged Release
|
name: Build Tagged Release
|
||||||
uses: ./.github/workflows/partial-builder.yml
|
uses: ./.github/workflows/partial-builder.yml
|
||||||
needs:
|
needs:
|
||||||
@@ -42,4 +47,34 @@ jobs:
|
|||||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_RELEASE_WEBHOOK }}
|
DISCORD_WEBHOOK: ${{ secrets.DISCORD_RELEASE_WEBHOOK }}
|
||||||
uses: Ilshidur/action-discord@0.3.2
|
uses: Ilshidur/action-discord@0.3.2
|
||||||
with:
|
with:
|
||||||
args: "🚀 Version {{ EVENT_PAYLOAD.release.tag_name }} of Mealie has been released. See the release notes https://github.com/hay-kot/mealie/releases/tag/{{ EVENT_PAYLOAD.release.tag_name }}"
|
args: "🚀 Version {{ EVENT_PAYLOAD.release.tag_name }} of Mealie has been released. See the release notes https://github.com/mealie-recipes/mealie/releases/tag/{{ EVENT_PAYLOAD.release.tag_name }}"
|
||||||
|
|
||||||
|
update-image-tags:
|
||||||
|
name: Update image tag in sample docker-compose files
|
||||||
|
needs:
|
||||||
|
- build-release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout 🛎
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Modify version strings
|
||||||
|
run: |
|
||||||
|
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:${{ github.event.release.tag_name }}/' docs/docs/documentation/getting-started/installation/sqlite.md
|
||||||
|
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:${{ github.event.release.tag_name }}/' docs/docs/documentation/getting-started/installation/postgres.md
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@v6
|
||||||
|
# This doesn't currently work for us because it creates the PR but the workflows don't run.
|
||||||
|
# TODO: Provide a personal access token as a parameter here, that solves that problem.
|
||||||
|
# https://github.com/peter-evans/create-pull-request
|
||||||
|
with:
|
||||||
|
commit-message: "Update image tag, for release ${{ github.event.release.tag_name }}"
|
||||||
|
branch: "docs/newrelease-update-version-${{ github.event.release.tag_name }}"
|
||||||
|
delete-branch: true
|
||||||
|
base: mealie-next
|
||||||
|
title: "docs(auto): Update image tag, for release ${{ github.event.release.tag_name }}"
|
||||||
|
body: "Auto-generated by `.github/workflows/release.yml`, on publish of release ${{ github.event.release.tag_name }}"
|
||||||
|
|||||||
7
.github/workflows/stale.yml
vendored
@@ -4,6 +4,10 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: '30 1 * * *'
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -22,9 +26,6 @@ jobs:
|
|||||||
days-before-pr-close: -1
|
days-before-pr-close: -1
|
||||||
# If an issue/PR has a milestone, it's exempt from being marked as stale.
|
# If an issue/PR has a milestone, it's exempt from being marked as stale.
|
||||||
exempt-all-milestones: true
|
exempt-all-milestones: true
|
||||||
# For initial implementation - this stops any actual updates happening.
|
|
||||||
# We can review the output and then decide how to proceed. I will own this action.
|
|
||||||
debug-only: true
|
|
||||||
# How many API calls will we allow the action to make, essentially.
|
# How many API calls will we allow the action to make, essentially.
|
||||||
# Doco: https://github.com/actions/stale?tab=readme-ov-file#operations-per-run
|
# Doco: https://github.com/actions/stale?tab=readme-ov-file#operations-per-run
|
||||||
operations-per-run: 150
|
operations-per-run: 150
|
||||||
|
|||||||
1
.gitignore
vendored
@@ -162,3 +162,4 @@ lcov.info
|
|||||||
dev/code-generation/openapi.json
|
dev/code-generation/openapi.json
|
||||||
|
|
||||||
.run/
|
.run/
|
||||||
|
.task/*
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ repos:
|
|||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
exclude: ^tests/data/
|
exclude: ^tests/data/
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 23.1.0
|
rev: 24.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
|
|||||||
23
.vscode/tasks.json
vendored
@@ -1,22 +1,9 @@
|
|||||||
{
|
{
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
|
||||||
"label": "DEV: Build and Start Docker Compose",
|
|
||||||
"command": "make docker-dev",
|
|
||||||
"type": "shell",
|
|
||||||
"args": [],
|
|
||||||
"problemMatcher": [
|
|
||||||
"$tsc"
|
|
||||||
],
|
|
||||||
"presentation": {
|
|
||||||
"reveal": "always"
|
|
||||||
},
|
|
||||||
"group": "test"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "Production: Build and Start Docker Compose",
|
"label": "Production: Build and Start Docker Compose",
|
||||||
"command": "make docker-prod",
|
"command": "task docker:prod",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"args": [],
|
"args": [],
|
||||||
"problemMatcher": [
|
"problemMatcher": [
|
||||||
@@ -29,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Dev: Start Backend",
|
"label": "Dev: Start Backend",
|
||||||
"command": "make backend",
|
"command": "task py",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"reveal": "always",
|
"reveal": "always",
|
||||||
@@ -49,7 +36,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Dev: Start Frontend",
|
"label": "Dev: Start Frontend",
|
||||||
"command": "make frontend",
|
"command": "task ui",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"reveal": "always",
|
"reveal": "always",
|
||||||
@@ -59,7 +46,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Dev: Start Docs Server",
|
"label": "Dev: Start Docs Server",
|
||||||
"command": "make docs",
|
"command": "task docs",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"reveal": "always",
|
"reveal": "always",
|
||||||
@@ -69,7 +56,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Run python tests",
|
"label": "Run python tests",
|
||||||
"command": "make test",
|
"command": "task py:test",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"reveal": "always"
|
"reveal": "always"
|
||||||
|
|||||||
@@ -71,12 +71,9 @@ Distributed under the AGPL License. See `LICENSE` for more information.
|
|||||||
|
|
||||||
Huge thanks to all the sponsors of this project on [Github Sponsors](https://github.com/sponsors/hay-kot) and Buy Me a Coffee. Without you, this project would surely not be possible.
|
Huge thanks to all the sponsors of this project on [Github Sponsors](https://github.com/sponsors/hay-kot) and Buy Me a Coffee. Without you, this project would surely not be possible.
|
||||||
|
|
||||||
Thanks to Linode for providing Hosting for the Demo, Beta, and Documentation sites! Another big thanks to JetBrains for providing their IDEs for development.
|
Thanks to Depot for providing build instances for our Docker image builds.
|
||||||
|
|
||||||
<div align='center'>
|
[](https://depot.dev?utm_source=Mealie)
|
||||||
<img height="100" src="docs/docs/assets/img/sponsors-linode.svg" />
|
|
||||||
<img height="100" src="docs/docs/assets/img/sponsors-jetbrains.png" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -91,7 +88,7 @@ Thanks to Linode for providing Hosting for the Demo, Beta, and Documentation sit
|
|||||||
[stars-url]: https://github.com/mealie-recipes/mealie/stargazers
|
[stars-url]: https://github.com/mealie-recipes/mealie/stargazers
|
||||||
[issues-shield]: https://img.shields.io/github/issues/mealie-recipes/mealie.svg?style=flat-square
|
[issues-shield]: https://img.shields.io/github/issues/mealie-recipes/mealie.svg?style=flat-square
|
||||||
[issues-url]: https://github.com/mealie-recipes/mealie/issues
|
[issues-url]: https://github.com/mealie-recipes/mealie/issues
|
||||||
[latest-release-shield]: https://img.shields.io/github/v/release/mealie-recipes/mealie.svg?style=flat-square
|
[latest-release-shield]: https://img.shields.io/github/v/release/mealie-recipes/mealie?style=flat-square&label=latest%20release
|
||||||
[latest-release-url]: https://img.shields.io/github/v/release/mealie-recipes/mealie
|
[latest-release-url]: https://img.shields.io/github/v/release/mealie-recipes/mealie
|
||||||
[license-shield]: https://img.shields.io/github/license/mealie-recipes/mealie.svg?style=flat-square
|
[license-shield]: https://img.shields.io/github/license/mealie-recipes/mealie.svg?style=flat-square
|
||||||
[license-url]: https://github.com/mealie-recipes/mealie/blob/mealie-next/LICENSE
|
[license-url]: https://github.com/mealie-recipes/mealie/blob/mealie-next/LICENSE
|
||||||
|
|||||||
185
Taskfile.yml
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
# https://taskfile.dev
|
||||||
|
|
||||||
|
version: "3"
|
||||||
|
vars:
|
||||||
|
GREETING: Hello, World!
|
||||||
|
env:
|
||||||
|
DEFAULT_GROUP: Home
|
||||||
|
PRODUCTION: false
|
||||||
|
API_PORT: 9000
|
||||||
|
API_DOCS: True
|
||||||
|
TOKEN_TIME: 256 # hours
|
||||||
|
# mailplit SMTP config
|
||||||
|
# start dev:services to use mailpit
|
||||||
|
SMTP_HOST: localhost
|
||||||
|
SMTP_PORT: 1025
|
||||||
|
SMTP_FROM_NAME: MealieDev
|
||||||
|
SMTP_FROM_EMAIL: mealie@example.com
|
||||||
|
SMTP_AUTH_STRATEGY: NONE
|
||||||
|
BASE_URL: http://localhost:3000
|
||||||
|
LANG: en-US
|
||||||
|
|
||||||
|
# loads .env file if it exists
|
||||||
|
dotenv:
|
||||||
|
- .env
|
||||||
|
- .dev.env
|
||||||
|
tasks:
|
||||||
|
docs:gen:
|
||||||
|
desc: runs the API documentation generator
|
||||||
|
cmds:
|
||||||
|
- poetry run python dev/code-generation/gen_docs_api.py
|
||||||
|
|
||||||
|
docs:
|
||||||
|
desc: runs the documentation server
|
||||||
|
dir: docs
|
||||||
|
deps:
|
||||||
|
- docs:gen
|
||||||
|
cmds:
|
||||||
|
- poetry run python -m mkdocs serve
|
||||||
|
|
||||||
|
setup:ui:
|
||||||
|
desc: setup frontend dependencies
|
||||||
|
dir: frontend
|
||||||
|
cmds:
|
||||||
|
- yarn install
|
||||||
|
|
||||||
|
setup:py:
|
||||||
|
desc: setup python dependencies
|
||||||
|
cmds:
|
||||||
|
- poetry install --with main,dev,postgres
|
||||||
|
- poetry run pre-commit install
|
||||||
|
|
||||||
|
setup:model:
|
||||||
|
desc: setup nlp model
|
||||||
|
vars:
|
||||||
|
MODEL_URL: https://github.com/mealie-recipes/nlp-model/releases/download/v1.0.0/model.crfmodel
|
||||||
|
OUTPUT: ./mealie/services/parser_services/crfpp/model.crfmodel
|
||||||
|
sources:
|
||||||
|
# using pyproject.toml as the dependency since this should only ever need to run once
|
||||||
|
# during setup. There is perhaps a better way to do this.
|
||||||
|
- ./pyproject.toml
|
||||||
|
generates:
|
||||||
|
- ./mealie/services/parser_services/crfpp/model.crfmodel
|
||||||
|
cmds:
|
||||||
|
- curl -L0 {{ .MODEL_URL }} --output {{ .OUTPUT }}
|
||||||
|
|
||||||
|
setup:
|
||||||
|
desc: setup all dependencies
|
||||||
|
deps:
|
||||||
|
- setup:ui
|
||||||
|
- setup:py
|
||||||
|
- setup:model
|
||||||
|
|
||||||
|
dev:generate:
|
||||||
|
desc: run code generators
|
||||||
|
cmds:
|
||||||
|
- poetry run python dev/code-generation/main.py
|
||||||
|
|
||||||
|
dev:services:
|
||||||
|
desc: starts postgres and mailpit containers
|
||||||
|
dir: docker
|
||||||
|
cmds:
|
||||||
|
- docker compose -f docker-compose.dev.yml up
|
||||||
|
|
||||||
|
dev:clean:
|
||||||
|
desc: cleans up dev environment !! removes all data files !!
|
||||||
|
vars:
|
||||||
|
DEV_DATA: ""
|
||||||
|
cmds:
|
||||||
|
- rm -r ./dev/data/recipes/
|
||||||
|
- rm -r ./dev/data/users/
|
||||||
|
- rm -f ./dev/data/mealie*.db
|
||||||
|
- rm -f ./dev/data/mealie.log
|
||||||
|
- rm -f ./dev/data/.secret
|
||||||
|
|
||||||
|
py:mypy:
|
||||||
|
desc: runs python type checking
|
||||||
|
cmds:
|
||||||
|
- poetry run mypy mealie
|
||||||
|
|
||||||
|
py:test:
|
||||||
|
desc: runs python tests (support args after '--')
|
||||||
|
cmds:
|
||||||
|
- poetry run pytest {{ .CLI_ARGS }}
|
||||||
|
|
||||||
|
py:format:
|
||||||
|
desc: runs python code formatter
|
||||||
|
cmds:
|
||||||
|
- poetry run black mealie
|
||||||
|
|
||||||
|
py:lint:
|
||||||
|
desc: runs python linter
|
||||||
|
cmds:
|
||||||
|
- poetry run ruff check mealie
|
||||||
|
|
||||||
|
py:check:
|
||||||
|
desc: runs all linters, type checkers, and formatters
|
||||||
|
deps:
|
||||||
|
- py:format
|
||||||
|
- py:lint
|
||||||
|
- py:mypy
|
||||||
|
- py:test
|
||||||
|
|
||||||
|
py:coverage:
|
||||||
|
desc: runs python coverage and generates html report
|
||||||
|
cmds:
|
||||||
|
- poetry run pytest
|
||||||
|
- poetry run coverage report -m
|
||||||
|
- poetry run coveragepy-lcov
|
||||||
|
- poetry run coverage html
|
||||||
|
- open htmlcov/index.html
|
||||||
|
|
||||||
|
py:
|
||||||
|
desc: runs the backend server
|
||||||
|
cmds:
|
||||||
|
- poetry run python mealie/db/init_db.py
|
||||||
|
- poetry run python mealie/app.py
|
||||||
|
|
||||||
|
py:postgres:
|
||||||
|
desc: runs the backend server configured for containerized postgres
|
||||||
|
env:
|
||||||
|
DB_ENGINE: postgres
|
||||||
|
POSTGRES_USER: mealie
|
||||||
|
POSTGRES_PASSWORD: mealie
|
||||||
|
POSTGRES_SERVER: localhost
|
||||||
|
POSTGRES_PORT: 5432
|
||||||
|
POSTGRES_DB: mealie
|
||||||
|
cmds:
|
||||||
|
- poetry run python mealie/db/init_db.py
|
||||||
|
- poetry run python mealie/app.py
|
||||||
|
|
||||||
|
ui:build:
|
||||||
|
desc: builds the frontend in frontend/dist
|
||||||
|
dir: frontend
|
||||||
|
cmds:
|
||||||
|
- yarn build
|
||||||
|
|
||||||
|
ui:lint:
|
||||||
|
desc: runs the frontend linter
|
||||||
|
dir: frontend
|
||||||
|
cmds:
|
||||||
|
- yarn lint
|
||||||
|
|
||||||
|
ui:test:
|
||||||
|
desc: runs the frontend tests
|
||||||
|
dir: frontend
|
||||||
|
cmds:
|
||||||
|
- yarn test
|
||||||
|
|
||||||
|
ui:check:
|
||||||
|
desc: runs all frontend checks
|
||||||
|
deps:
|
||||||
|
- ui:lint
|
||||||
|
- ui:test
|
||||||
|
|
||||||
|
ui:
|
||||||
|
desc: runs the frontend server
|
||||||
|
dir: frontend
|
||||||
|
cmds:
|
||||||
|
- yarn run dev
|
||||||
|
|
||||||
|
docker:prod:
|
||||||
|
desc: builds and runs the production docker image locally
|
||||||
|
dir: docker
|
||||||
|
cmds:
|
||||||
|
- docker compose -f docker-compose.yml -p mealie up -d --build
|
||||||
@@ -5,6 +5,7 @@ Revises:
|
|||||||
Create Date: 2022-02-21 19:56:24.351115
|
Create Date: 2022-02-21 19:56:24.351115
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import engine_from_config
|
from sqlalchemy import engine_from_config
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 6b0f5f32d602
|
|||||||
Create Date: 2022-03-23 17:43:34.727829
|
Create Date: 2022-03-23 17:43:34.727829
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 263dd6707191
|
|||||||
Create Date: 2022-03-27 19:30:28.545846
|
Create Date: 2022-03-27 19:30:28.545846
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: f1a2dbee5fe9
|
|||||||
Create Date: 2022-03-31 19:19:55.428965
|
Create Date: 2022-03-31 19:19:55.428965
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
import mealie.db.migration_types
|
import mealie.db.migration_types
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 59eb59135381
|
|||||||
Create Date: 2022-04-03 10:48:51.379968
|
Create Date: 2022-04-03 10:48:51.379968
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
import mealie.db.migration_types # noqa: F401
|
import mealie.db.migration_types # noqa: F401
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 09dfc897ad62
|
|||||||
Create Date: 2022-06-01 11:12:06.748383
|
Create Date: 2022-06-01 11:12:06.748383
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ Revises: ab0bae02578f
|
|||||||
Create Date: 2022-06-15 21:05:34.851857
|
Create Date: 2022-06-15 21:05:34.851857
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: f30cf048c228
|
|||||||
Create Date: 2022-08-12 19:05:59.776361
|
Create Date: 2022-08-12 19:05:59.776361
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 188374910655
|
|||||||
Create Date: 2022-08-05 17:07:07.389271
|
Create Date: 2022-08-05 17:07:07.389271
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 089bfa50d0ed
|
|||||||
Create Date: 2022-08-29 13:57:40.452245
|
Create Date: 2022-08-29 13:57:40.452245
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
import mealie.db.migration_types
|
import mealie.db.migration_types
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 44e8d670719d
|
|||||||
Create Date: 2022-09-27 14:53:14.111054
|
Create Date: 2022-09-27 14:53:14.111054
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
import mealie.db.migration_types
|
import mealie.db.migration_types
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 2ea7a807915c
|
|||||||
Create Date: 2022-11-03 13:10:24.811134
|
Create Date: 2022-11-03 13:10:24.811134
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 1923519381ad
|
|||||||
Create Date: 2022-11-22 03:42:45.494567
|
Create Date: 2022-11-22 03:42:45.494567
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 167eb69066ad
|
|||||||
Create Date: 2023-01-21 16:54:44.368768
|
Create Date: 2023-01-21 16:54:44.368768
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
import mealie.db.migration_types
|
import mealie.db.migration_types
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: ff5f73b01a7a
|
|||||||
Create Date: 2023-02-10 21:18:32.405130
|
Create Date: 2023-02-10 21:18:32.405130
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
import mealie.db.migration_types
|
import mealie.db.migration_types
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 16160bf731a0
|
|||||||
Create Date: 2023-02-14 20:45:41.102571
|
Create Date: 2023-02-14 20:45:41.102571
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import orm, select
|
from sqlalchemy import orm, select
|
||||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ Revises: 5ab195a474eb
|
|||||||
Create Date: 2023-21-02 22:03:19.837244
|
Create Date: 2023-21-02 22:03:19.837244
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy import orm
|
||||||
|
|
||||||
import mealie.db.migration_types
|
import mealie.db.migration_types
|
||||||
from alembic import op
|
from alembic import op
|
||||||
@@ -22,8 +23,10 @@ branch_labels = None
|
|||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
def populate_shopping_lists_multi_purpose_labels(shopping_lists_multi_purpose_labels_table: sa.Table, session: Session):
|
def populate_shopping_lists_multi_purpose_labels(
|
||||||
shopping_lists = session.query(ShoppingList).all()
|
shopping_lists_multi_purpose_labels_table: sa.Table, session: orm.Session
|
||||||
|
):
|
||||||
|
shopping_lists = session.query(ShoppingList).options(orm.load_only(ShoppingList.id, ShoppingList.group_id)).all()
|
||||||
|
|
||||||
shopping_lists_labels_data: list[dict] = []
|
shopping_lists_labels_data: list[dict] = []
|
||||||
for shopping_list in shopping_lists:
|
for shopping_list in shopping_lists:
|
||||||
@@ -59,7 +62,7 @@ def upgrade():
|
|||||||
)
|
)
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
session = Session(bind=op.get_bind())
|
session = orm.Session(bind=op.get_bind())
|
||||||
populate_shopping_lists_multi_purpose_labels(shopping_lists_multi_purpose_labels_table, session)
|
populate_shopping_lists_multi_purpose_labels(shopping_lists_multi_purpose_labels_table, session)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: b04a08da2108
|
|||||||
Create Date: 2023-02-22 21:45:52.900964
|
Create Date: 2023-02-22 21:45:52.900964
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
import mealie.db.migration_types
|
import mealie.db.migration_types
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 38514b39a824
|
|||||||
Create Date: 2023-04-13 06:47:04.617131
|
Create Date: 2023-04-13 06:47:04.617131
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
import mealie.db.migration_types
|
import mealie.db.migration_types
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: b3dbb554ba53
|
|||||||
Create Date: 2023-08-06 21:00:34.582905
|
Create Date: 2023-08-06 21:00:34.582905
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 04ac51cbe9a4
|
|||||||
Create Date: 2023-08-14 19:30:49.103185
|
Create Date: 2023-08-14 19:30:49.103185
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 1825b5225403
|
|||||||
Create Date: 2023-08-15 16:25:07.058929
|
Create Date: 2023-08-15 16:25:07.058929
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: bcfdad6b7355
|
|||||||
Create Date: 2023-09-01 14:55:42.166766
|
Create Date: 2023-09-01 14:55:42.166766
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import orm, select
|
from sqlalchemy import orm, select
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 0341b154f79a
|
|||||||
Create Date: 2023-10-04 14:29:26.688065
|
Create Date: 2023-10-04 14:29:26.688065
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -58,7 +59,12 @@ def _resolve_duplicate_food(
|
|||||||
keep_food_id: UUID4,
|
keep_food_id: UUID4,
|
||||||
dupe_food_id: UUID4,
|
dupe_food_id: UUID4,
|
||||||
):
|
):
|
||||||
for shopping_list_item in session.query(ShoppingListItem).filter_by(food_id=dupe_food_id).all():
|
for shopping_list_item in (
|
||||||
|
session.query(ShoppingListItem)
|
||||||
|
.options(load_only(ShoppingListItem.id, ShoppingListItem.food_id))
|
||||||
|
.filter_by(food_id=dupe_food_id)
|
||||||
|
.all()
|
||||||
|
):
|
||||||
shopping_list_item.food_id = keep_food_id
|
shopping_list_item.food_id = keep_food_id
|
||||||
|
|
||||||
for recipe_ingredient in (
|
for recipe_ingredient in (
|
||||||
@@ -81,10 +87,20 @@ def _resolve_duplicate_unit(
|
|||||||
keep_unit_id: UUID4,
|
keep_unit_id: UUID4,
|
||||||
dupe_unit_id: UUID4,
|
dupe_unit_id: UUID4,
|
||||||
):
|
):
|
||||||
for shopping_list_item in session.query(ShoppingListItem).filter_by(unit_id=dupe_unit_id).all():
|
for shopping_list_item in (
|
||||||
|
session.query(ShoppingListItem)
|
||||||
|
.options(load_only(ShoppingListItem.id, ShoppingListItem.unit_id))
|
||||||
|
.filter_by(unit_id=dupe_unit_id)
|
||||||
|
.all()
|
||||||
|
):
|
||||||
shopping_list_item.unit_id = keep_unit_id
|
shopping_list_item.unit_id = keep_unit_id
|
||||||
|
|
||||||
for recipe_ingredient in session.query(RecipeIngredientModel).filter_by(unit_id=dupe_unit_id).all():
|
for recipe_ingredient in (
|
||||||
|
session.query(RecipeIngredientModel)
|
||||||
|
.options(load_only(RecipeIngredientModel.id, RecipeIngredientModel.unit_id))
|
||||||
|
.filter_by(unit_id=dupe_unit_id)
|
||||||
|
.all()
|
||||||
|
):
|
||||||
recipe_ingredient.unit_id = keep_unit_id
|
recipe_ingredient.unit_id = keep_unit_id
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
@@ -99,10 +115,20 @@ def _resolve_duplicate_label(
|
|||||||
keep_label_id: UUID4,
|
keep_label_id: UUID4,
|
||||||
dupe_label_id: UUID4,
|
dupe_label_id: UUID4,
|
||||||
):
|
):
|
||||||
for shopping_list_item in session.query(ShoppingListItem).filter_by(label_id=dupe_label_id).all():
|
for shopping_list_item in (
|
||||||
|
session.query(ShoppingListItem)
|
||||||
|
.options(load_only(ShoppingListItem.id, ShoppingListItem.label_id))
|
||||||
|
.filter_by(label_id=dupe_label_id)
|
||||||
|
.all()
|
||||||
|
):
|
||||||
shopping_list_item.label_id = keep_label_id
|
shopping_list_item.label_id = keep_label_id
|
||||||
|
|
||||||
for ingredient_food in session.query(IngredientFoodModel).filter_by(label_id=dupe_label_id).all():
|
for ingredient_food in (
|
||||||
|
session.query(IngredientFoodModel)
|
||||||
|
.options(load_only(IngredientFoodModel.id, IngredientFoodModel.label_id))
|
||||||
|
.filter_by(label_id=dupe_label_id)
|
||||||
|
.all()
|
||||||
|
):
|
||||||
ingredient_food.label_id = keep_label_id
|
ingredient_food.label_id = keep_label_id
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: dded3119c1fe
|
|||||||
Create Date: 2023-10-19 19:22:55.369319
|
Create Date: 2023-10-19 19:22:55.369319
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
import mealie.db.migration_types
|
import mealie.db.migration_types
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
"""added user to shopping list
|
||||||
|
|
||||||
|
Revision ID: 2298bb460ffd
|
||||||
|
Revises: ba1e4a6cfe99
|
||||||
|
Create Date: 2024-02-23 16:15:07.115641
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
import mealie.db.migration_types
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "2298bb460ffd"
|
||||||
|
down_revision = "ba1e4a6cfe99"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def is_postgres():
|
||||||
|
return op.get_context().dialect.name == "postgresql"
|
||||||
|
|
||||||
|
|
||||||
|
def find_user_id_for_group(group_id: UUID):
|
||||||
|
bind = op.get_bind()
|
||||||
|
session = orm.Session(bind=bind)
|
||||||
|
|
||||||
|
if is_postgres():
|
||||||
|
stmt = "SELECT id FROM users WHERE group_id=:group_id AND admin = TRUE LIMIT 1"
|
||||||
|
else:
|
||||||
|
stmt = "SELECT id FROM users WHERE group_id=:group_id AND admin = 1 LIMIT 1"
|
||||||
|
|
||||||
|
with session:
|
||||||
|
try:
|
||||||
|
# try to find an admin user
|
||||||
|
user_id = session.execute(sa.text(stmt).bindparams(group_id=group_id)).scalar_one()
|
||||||
|
except orm.exc.NoResultFound:
|
||||||
|
# fallback to any user
|
||||||
|
user_id = session.execute(
|
||||||
|
sa.text("SELECT id FROM users WHERE group_id=:group_id LIMIT 1").bindparams(group_id=group_id)
|
||||||
|
).scalar_one()
|
||||||
|
return user_id
|
||||||
|
|
||||||
|
|
||||||
|
def populate_shopping_list_users():
|
||||||
|
bind = op.get_bind()
|
||||||
|
session = orm.Session(bind=bind)
|
||||||
|
|
||||||
|
with session:
|
||||||
|
list_ids_and_group_ids = session.execute(sa.text("SELECT id, group_id FROM shopping_lists")).all()
|
||||||
|
for list_id, group_id in list_ids_and_group_ids:
|
||||||
|
user_id = find_user_id_for_group(group_id)
|
||||||
|
session.execute(
|
||||||
|
sa.text(f"UPDATE shopping_lists SET user_id=:user_id WHERE id=:id").bindparams(
|
||||||
|
user_id=user_id, id=list_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table("shopping_lists") as batch_op:
|
||||||
|
# allow nulls during migration
|
||||||
|
batch_op.add_column(sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=True))
|
||||||
|
batch_op.create_index(op.f("ix_shopping_lists_user_id"), ["user_id"], unique=False)
|
||||||
|
batch_op.create_foreign_key("fk_user_shopping_lists", "users", ["user_id"], ["id"])
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
populate_shopping_list_users()
|
||||||
|
|
||||||
|
# forbid nulls after migration
|
||||||
|
with op.batch_alter_table("shopping_lists") as batch_op:
|
||||||
|
batch_op.alter_column("user_id", nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint(None, "shopping_lists", type_="foreignkey")
|
||||||
|
op.drop_index(op.f("ix_shopping_lists_user_id"), table_name="shopping_lists")
|
||||||
|
op.drop_column("shopping_lists", "user_id")
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -3,8 +3,8 @@ from pathlib import Path
|
|||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, ConfigDict
|
||||||
from utils import PROJECT_DIR, CodeTemplates, HTTPRequest, RouteObject
|
from utils import PROJECT_DIR, CodeTemplates, HTTPRequest, RouteObject, RequestType
|
||||||
|
|
||||||
CWD = Path(__file__).parent
|
CWD = Path(__file__).parent
|
||||||
|
|
||||||
@@ -12,23 +12,25 @@ OUTFILE = PROJECT_DIR / "tests" / "utils" / "api_routes" / "__init__.py"
|
|||||||
|
|
||||||
|
|
||||||
class PathObject(BaseModel):
|
class PathObject(BaseModel):
|
||||||
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||||
route_object: RouteObject
|
route_object: RouteObject
|
||||||
http_verbs: list[HTTPRequest]
|
http_verbs: list[HTTPRequest]
|
||||||
|
|
||||||
class Config:
|
|
||||||
arbitrary_types_allowed = True
|
|
||||||
|
|
||||||
|
|
||||||
def get_path_objects(app: FastAPI):
|
def get_path_objects(app: FastAPI):
|
||||||
paths = []
|
paths = []
|
||||||
|
|
||||||
for key, value in app.openapi().items():
|
for key, value in app.openapi().items():
|
||||||
if key == "paths":
|
if key == "paths":
|
||||||
for key, value in value.items():
|
for key, value2 in value.items():
|
||||||
|
verbs = []
|
||||||
|
for k, v in value2.items():
|
||||||
|
verbs.append(HTTPRequest(request_type=k, **v))
|
||||||
|
|
||||||
paths.append(
|
paths.append(
|
||||||
PathObject(
|
PathObject(
|
||||||
route_object=RouteObject(key),
|
route_object=RouteObject(key),
|
||||||
http_verbs=[HTTPRequest(request_type=k, **v) for k, v in value.items()],
|
http_verbs=verbs,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
|||||||
import dotenv
|
import dotenv
|
||||||
import requests
|
import requests
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
from pydantic import Extra
|
from pydantic import ConfigDict
|
||||||
from requests import Response
|
from requests import Response
|
||||||
from utils import CodeDest, CodeKeys, inject_inline, log
|
from utils import CodeDest, CodeKeys, inject_inline, log
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ LOCALE_DATA: dict[str, LocaleData] = {
|
|||||||
"zh-TW": LocaleData(name="繁體中文 (Chinese traditional)"),
|
"zh-TW": LocaleData(name="繁體中文 (Chinese traditional)"),
|
||||||
}
|
}
|
||||||
|
|
||||||
LOCALE_TEMPLATE = """// This Code is auto generated by gen_global_components.py
|
LOCALE_TEMPLATE = """// This Code is auto generated by gen_ts_locales.py
|
||||||
export const LOCALES = [{% for locale in locales %}
|
export const LOCALES = [{% for locale in locales %}
|
||||||
{
|
{
|
||||||
name: "{{ locale.name }}",
|
name: "{{ locale.name }}",
|
||||||
@@ -70,6 +70,8 @@ export const LOCALES = [{% for locale in locales %}
|
|||||||
|
|
||||||
|
|
||||||
class TargetLanguage(MealieModel):
|
class TargetLanguage(MealieModel):
|
||||||
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
||||||
|
|
||||||
id: str
|
id: str
|
||||||
name: str
|
name: str
|
||||||
locale: str
|
locale: str
|
||||||
@@ -78,10 +80,6 @@ class TargetLanguage(MealieModel):
|
|||||||
twoLettersCode: str
|
twoLettersCode: str
|
||||||
progress: float = 0.0
|
progress: float = 0.0
|
||||||
|
|
||||||
class Config:
|
|
||||||
extra = Extra.allow
|
|
||||||
allow_population_by_field_name = True
|
|
||||||
|
|
||||||
|
|
||||||
class CrowdinApi:
|
class CrowdinApi:
|
||||||
project_name = "Mealie"
|
project_name = "Mealie"
|
||||||
@@ -152,6 +150,7 @@ PROJECT_DIR = Path(__file__).parent.parent.parent
|
|||||||
datetime_dir = PROJECT_DIR / "frontend" / "lang" / "dateTimeFormats"
|
datetime_dir = PROJECT_DIR / "frontend" / "lang" / "dateTimeFormats"
|
||||||
locales_dir = PROJECT_DIR / "frontend" / "lang" / "messages"
|
locales_dir = PROJECT_DIR / "frontend" / "lang" / "messages"
|
||||||
nuxt_config = PROJECT_DIR / "frontend" / "nuxt.config.js"
|
nuxt_config = PROJECT_DIR / "frontend" / "nuxt.config.js"
|
||||||
|
reg_valid = PROJECT_DIR / "mealie" / "schema" / "_mealie" / "validators.py"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This snippet walks the message and dat locales directories and generates the import information
|
This snippet walks the message and dat locales directories and generates the import information
|
||||||
@@ -175,6 +174,19 @@ def inject_nuxt_values():
|
|||||||
inject_inline(nuxt_config, CodeKeys.nuxt_local_dates, all_date_locales)
|
inject_inline(nuxt_config, CodeKeys.nuxt_local_dates, all_date_locales)
|
||||||
|
|
||||||
|
|
||||||
|
def inject_registration_validation_values():
|
||||||
|
all_langs = []
|
||||||
|
for match in locales_dir.glob("*.json"):
|
||||||
|
lang_string = f'"{match.stem}",'
|
||||||
|
all_langs.append(lang_string)
|
||||||
|
|
||||||
|
# sort
|
||||||
|
all_langs.sort()
|
||||||
|
|
||||||
|
log.debug(f"injecting locales into user registration validation -> {reg_valid}")
|
||||||
|
inject_inline(reg_valid, CodeKeys.nuxt_local_messages, all_langs)
|
||||||
|
|
||||||
|
|
||||||
def generate_locales_ts_file():
|
def generate_locales_ts_file():
|
||||||
api = CrowdinApi("")
|
api = CrowdinApi("")
|
||||||
models = api.get_languages()
|
models = api.get_languages()
|
||||||
@@ -193,6 +205,7 @@ def main():
|
|||||||
|
|
||||||
generate_locales_ts_file()
|
generate_locales_ts_file()
|
||||||
inject_nuxt_values()
|
inject_nuxt_values()
|
||||||
|
inject_registration_validation_values()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from utils import log
|
|||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
template = """// This Code is auto generated by gen_global_components.py
|
template = """// This Code is auto generated by gen_ts_types.py
|
||||||
{% for name in global %}import {{ name }} from "@/components/global/{{ name }}.vue";
|
{% for name in global %}import {{ name }} from "@/components/global/{{ name }}.vue";
|
||||||
{% endfor %}{% for name in layout %}import {{ name }} from "@/components/layout/{{ name }}.vue";
|
{% endfor %}{% for name in layout %}import {{ name }} from "@/components/layout/{{ name }}.vue";
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import re
|
import re
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from humps import camelize
|
from humps import camelize
|
||||||
from pydantic import BaseModel, Extra, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
|
|
||||||
|
|
||||||
@@ -34,33 +33,30 @@ class ParameterIn(str, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class RouterParameter(BaseModel):
|
class RouterParameter(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="allow")
|
||||||
|
|
||||||
required: bool = False
|
required: bool = False
|
||||||
name: str
|
name: str
|
||||||
location: ParameterIn = Field(..., alias="in")
|
location: ParameterIn = Field(..., alias="in")
|
||||||
|
|
||||||
class Config:
|
|
||||||
extra = Extra.allow
|
|
||||||
|
|
||||||
|
|
||||||
class RequestBody(BaseModel):
|
class RequestBody(BaseModel):
|
||||||
required: bool = False
|
model_config = ConfigDict(extra="allow")
|
||||||
|
|
||||||
class Config:
|
required: bool = False
|
||||||
extra = Extra.allow
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPRequest(BaseModel):
|
class HTTPRequest(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||||
|
|
||||||
request_type: RequestType
|
request_type: RequestType
|
||||||
description: str = ""
|
description: str = ""
|
||||||
summary: str
|
summary: str
|
||||||
requestBody: Optional[RequestBody]
|
request_body: RequestBody | None = None
|
||||||
|
|
||||||
parameters: list[RouterParameter] = []
|
parameters: list[RouterParameter] = []
|
||||||
tags: list[str] | None = []
|
tags: list[str] | None = []
|
||||||
|
|
||||||
class Config:
|
|
||||||
extra = Extra.allow
|
|
||||||
|
|
||||||
def list_as_js_object_string(self, parameters, braces=True):
|
def list_as_js_object_string(self, parameters, braces=True):
|
||||||
if len(parameters) == 0:
|
if len(parameters) == 0:
|
||||||
return ""
|
return ""
|
||||||
@@ -71,11 +67,11 @@ class HTTPRequest(BaseModel):
|
|||||||
return ", ".join(parameters)
|
return ", ".join(parameters)
|
||||||
|
|
||||||
def payload(self):
|
def payload(self):
|
||||||
return "payload" if self.requestBody else ""
|
return "payload" if self.request_body else ""
|
||||||
|
|
||||||
def function_args(self):
|
def function_args(self):
|
||||||
all_params = [p.name for p in self.parameters]
|
all_params = [p.name for p in self.parameters]
|
||||||
if self.requestBody:
|
if self.request_body:
|
||||||
all_params.append("payload")
|
all_params.append("payload")
|
||||||
return self.list_as_js_object_string(all_params)
|
return self.list_as_js_object_string(all_params)
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class CodeSlicer:
|
|||||||
self._next_line += 1
|
self._next_line += 1
|
||||||
|
|
||||||
|
|
||||||
def get_indentation_of_string(line: str, comment_char: str = "//") -> str:
|
def get_indentation_of_string(line: str, comment_char: str = "//|#") -> str:
|
||||||
return re.sub(rf"{comment_char}.*", "", line).removesuffix("\n")
|
return re.sub(rf"{comment_char}.*", "", line).removesuffix("\n")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
21
docker/docker-compose.dev.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
version: "3.4"
|
||||||
|
services:
|
||||||
|
mailpit:
|
||||||
|
image: axllent/mailpit:latest
|
||||||
|
container_name: mealie_dev_mailpit
|
||||||
|
restart: no
|
||||||
|
environment:
|
||||||
|
- "MP_SMTP_AUTH_ACCEPT_ANY=true"
|
||||||
|
- "MP_SMTP_AUTH_ALLOW_INSECURE=true"
|
||||||
|
ports:
|
||||||
|
- "8025:8025"
|
||||||
|
- "1025:1025"
|
||||||
|
postgres:
|
||||||
|
container_name: mealie_dev_postgres
|
||||||
|
image: postgres:15
|
||||||
|
restart: no
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: mealie
|
||||||
|
POSTGRES_USER: mealie
|
||||||
@@ -12,18 +12,18 @@ add_user() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
change_user() {
|
change_user() {
|
||||||
|
if [ "$(id -u)" = $PUID ]; then
|
||||||
|
echo "
|
||||||
|
User uid: $PUID
|
||||||
|
User gid: $PGID
|
||||||
|
"
|
||||||
|
elif [ "$(id -u)" = "0" ]; then
|
||||||
# If container is started as root then create a new user and switch to it
|
# If container is started as root then create a new user and switch to it
|
||||||
if [ "$(id -u)" = "0" ]; then
|
|
||||||
add_user
|
add_user
|
||||||
chown -R $PUID:$PGID /app
|
chown -R $PUID:$PGID /app
|
||||||
|
|
||||||
echo "Switching to dedicated user"
|
echo "Switching to dedicated user"
|
||||||
exec gosu $PUID "$BASH_SOURCE" "$@"
|
exec gosu $PUID "$BASH_SOURCE" "$@"
|
||||||
elif [ "$(id -u)" = $PUID ]; then
|
|
||||||
echo "
|
|
||||||
User uid: $PUID
|
|
||||||
User gid: $PGID
|
|
||||||
"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
docs/docs/assets/img/ios/api.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/docs/assets/img/ios/gemini.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/docs/assets/img/ios/setup.png
Normal file
|
After Width: | Height: | Size: 183 KiB |
BIN
docs/docs/assets/img/ios/url.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 88 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 230 90" width="2500" height="978"><path d="M93.8 27.8l5.8-1.4v28c0 3.1.9 4.9 2.7 5.5-.9 1.7-2.4 2.6-4.6 2.6-2.6 0-4-1.8-4-5.5V27.8zM108.4 62V41.8h-3.2V37h9.1v25h-5.9zm3-34.6c.9 0 1.7.3 2.4 1s1 1.5 1 2.4c0 .9-.3 1.7-1 2.4s-1.5 1-2.4 1c-.9 0-1.7-.3-2.4-1s-1-1.5-1-2.4c0-.9.3-1.7 1-2.4s1.5-1 2.4-1zM137.1 62V47.6c0-2.1-.4-3.7-1.2-4.6-.8-1-2.1-1.5-4-1.5-.9 0-1.8.2-2.7.7-1 .5-1.7 1.1-2.3 1.8v18h-5.8V37.1h4.2l1.1 2.3c1.6-1.9 3.9-2.8 7-2.8 3 0 5.3.9 7 2.7 1.7 1.8 2.6 4.3 2.6 7.4V62h-5.9zM147.5 49.5c0-3.8 1.1-6.9 3.3-9.3 2.2-2.4 5.1-3.6 8.7-3.6 3.8 0 6.7 1.1 8.8 3.4 2.1 2.3 3.1 5.4 3.1 9.4s-1.1 7.1-3.2 9.5c-2.1 2.3-5 3.5-8.8 3.5-3.8 0-6.7-1.2-8.8-3.5-2-2.4-3.1-5.5-3.1-9.4zm6.1 0c0 5.5 2 8.2 5.9 8.2 1.8 0 3.2-.7 4.3-2.1 1.1-1.4 1.6-3.5 1.6-6.1 0-5.4-2-8.1-5.9-8.1-1.8 0-3.3.7-4.3 2.1-1.1 1.4-1.6 3.4-1.6 6zM192.1 62v-1.5c-.5.5-1.3 1-2.4 1.4-1.1.4-2.3.6-3.6.6-3.5 0-6.2-1.1-8.2-3.3-2-2.2-3-5.3-3-9.2 0-3.9 1.1-7.1 3.4-9.6s5.1-3.7 8.6-3.7c1.9 0 3.6.4 5.2 1.2v-10l5.8-1.4V62h-5.8zm0-19c-1.2-1-2.5-1.5-3.9-1.5-2.3 0-4.1.7-5.4 2.1-1.3 1.4-1.9 3.5-1.9 6.1 0 5.2 2.5 7.8 7.5 7.8.6 0 1.2-.2 2.1-.5.8-.3 1.3-.7 1.6-1V43zM226 51.3h-17.8c.1 2 .8 3.5 2 4.6 1.3 1.1 2.9 1.7 5.1 1.7 2.6 0 4.7-.7 6-2.1l2.3 4.4c-2 1.7-5.1 2.5-9.2 2.5-3.8 0-6.8-1.1-9-3.3-2.2-2.2-3.3-5.3-3.3-9.3 0-3.9 1.2-7.1 3.6-9.5 2.4-2.4 5.3-3.6 8.7-3.6 3.6 0 6.5 1.1 8.7 3.2 2.2 2.2 3.3 4.9 3.3 8.2.1.7-.1 1.7-.4 3.2zm-17.6-4.4h12.2c-.4-3.6-2.4-5.5-6-5.5-3.3.1-5.4 1.9-6.2 5.5z"/><g><path fill="#004712" d="M65.9 47.4l-1 11.5-3.3-2.3.4-5.8v-.1-.1l-.1-.1-.1-.1-7.1-4.7.1-5.1 11.1 6.8zM48.5 59.9L43.4 56v.9c0 .2-.1.4-.2.5L39.4 60l4.2 3.4.1.1v.2l.2 4 4.7 3.9-.1-11.7zm-32.1 5l2.4 11.5 9.9 10.5L27 75.3 16.4 64.9zm9.3 1.7l-2.4-16.1-12-10 3.2 15.6 11.2 10.5zm-3.8-26l-3.3-22.8L4.8 9.2l4.5 21.5 12.6 9.9z"/><path fill="#00B259" d="M75.7 41.2l-1.5 10.9-8.2 6.6 1-11.2 8.7-6.3zM49.6 59.9l.1 11.8 10.5-8.4.7-11.5-11.3 8.1zm-6.8 4.8L28 75.3l1.8 12.2 13.4-10.7-.4-12.1zm-.4-8l-.7-16-17.3 9.9 2.4 16.6 15.6-10.5zm-1.1-25.3l-.9-21.6-20.8 8L23 41l18.3-9.6z"/><path d="M76.9 40c0-.1 0-.1 0 0v-.2s0-.1-.1-.1c0 0-.1 0-.1-.1l-12-6.7c-.2-.1-.4-.1-.5 0L54 39.1h-.1v.6l-.1 5.4-4.1-2.7c-.2-.1-.4-.1-.6 0L43 45.8l-.3-6v-.1-.1-.1-.1-.1h-.1l-6.2-4.1 5.8-3c.2-.1.3-.3.3-.5L41.4 9v-.1s0-.1-.1-.1c0 0 0-.1-.1-.1L25.5 1.1c-.1-.1-.2-.1-.3-.1L3.9 7.6s-.1 0-.1.1c0 0-.1 0-.1.1v.6l4.7 22.9c0 .1.1.2.2.3l6.4 5-4.7 2.2s-.1 0-.1.1c0 0 0 .1-.1.1v.2l3.6 17.2c0 .1.1.2.2.3l4.5 4.2-3 1.8-.1.1s0 .1-.1.1V63.2L18 76.5c0 .1.1.2.1.3l10.9 12h.1s.1 0 .1.1h.5l14.4-11.5c.1-.1.2-.3.2-.4l-.3-7.9 4.8 4s.1 0 .1.1h.5L61 64c.1-.1.2-.2.2-.4l.4-5.8 3.5 2.4h.4s.1 0 .1-.1l9.4-7.5c.1-.1.2-.2.2-.3L76.9 40c0 .1 0 .1 0 0zM66 58.7l1-11.2 8.8-6.3-1.5 10.9-8.3 6.6zm-4.4-2.1l.4-5.8v-.1-.1l-.1-.1-.1-.1-7.1-4.7.1-5.1 11.1 6.9-1 11.5-3.3-2.4zm-1.5 6.7l-10.5 8.4-.1-11.8 11.3-8.1-.7 11.5zM43.3 76.8L29.8 87.5 28 75.3l14.7-10.5.6 12zm-24.6-.4l-2.4-11.5L27 75.3l1.7 11.6-10-10.5zm-.2-58.6l3.3 22.8-12.5-9.9L4.8 9.2l13.7 8.6zm21.9-8l.9 21.6L23 41l-3.4-23.2 20.8-8zm2 46.9L26.8 67.1l-2.4-16.6 17.3-9.9.7 16.1zm-19.1-6.1l2.4 16.1-11.2-10.6-3.2-15.6 12 10.1zm20.5 13.1v-.1-.1l-.1-.1-4.2-3.4 3.8-2.6c.2-.1.2-.3.2-.5V56l5.1 3.9.1 11.8-4.7-3.9-.2-4.1z"/></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -5,15 +5,15 @@
|
|||||||
## We Develop with Github
|
## We Develop with Github
|
||||||
We use github to host code, to track issues and feature requests, as well as accept pull requests.
|
We use github to host code, to track issues and feature requests, as well as accept pull requests.
|
||||||
|
|
||||||
## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html), So All Code Changes Happen Through Pull Requests
|
## We Use [Github Flow](https://docs.github.com/en/get-started/using-github/github-flow), So All Code Changes Happen Through Pull Requests
|
||||||
Pull requests are the best way to propose changes to the codebase (we use [Github Flow](https://guides.github.com/introduction/flow/index.html)). We actively welcome your pull requests:
|
Pull requests are the best way to propose changes to the codebase (we use [Github Flow](https://docs.github.com/en/get-started/using-github/github-flow)). We actively welcome your pull requests:
|
||||||
|
|
||||||
1. Fork the repo and create your branch from `mealie-next`.
|
1. Fork the repo and create your branch from `mealie-next`.
|
||||||
2. Checkout the Discord, the PRs page, or the Projects page to get an idea of what's already being worked on.
|
2. Checkout the Discord, the PRs page, or the Projects page to get an idea of what's already being worked on.
|
||||||
3. If you're interested on working on major changes please get in touch on discord and coordinate with other developers. No sense in doubling up on work if someones already on it.
|
3. If you're interested on working on major changes please get in touch on discord and coordinate with other developers. No sense in doubling up on work if someones already on it.
|
||||||
4. Once you've got an idea of what changes you want to make, create a draft PR as soon as you can to let us know what you're working on and how we can help!
|
4. Once you've got an idea of what changes you want to make, create a draft PR as soon as you can to let us know what you're working on and how we can help!
|
||||||
5. If you've changed APIs, update the documentation.
|
5. If you've changed APIs, update the documentation.
|
||||||
6. Run tests, including `make backend-all`. Note that the tests do not clean up after themselves and leave things in the database. So be sure to also run `make clean-data` and/or `make backend-clean` inbetween major testing rounds to be sure that you aren't testing on old data.
|
6. Run tests, including `task py:check`.
|
||||||
6. Issue that pull request! First make a draft PR, make sure that the automated github tests all pass, then mark as ready for review.
|
6. Issue that pull request! First make a draft PR, make sure that the automated github tests all pass, then mark as ready for review.
|
||||||
7. Be sure to add release notes to the pull request.
|
7. Be sure to add release notes to the pull request.
|
||||||
|
|
||||||
@@ -28,8 +28,8 @@ We use GitHub issues to track public bugs. Report a bug by [opening a new issue]
|
|||||||
|
|
||||||
- A quick summary and/or background
|
- A quick summary and/or background
|
||||||
- Steps to reproduce
|
- Steps to reproduce
|
||||||
- Be specific!
|
* Be specific!
|
||||||
- Give sample code if you can. [This stackoverflow question](http://stackoverflow.com/q/12488905/180626) includes sample code that *anyone* with a base R setup can run to reproduce what I was seeing
|
* Give sample code if you can. [This stackoverflow question](http://stackoverflow.com/q/12488905/180626) includes sample code that *anyone* with a base R setup can run to reproduce what I was seeing
|
||||||
- What you expected would happen
|
- What you expected would happen
|
||||||
- What actually happens
|
- What actually happens
|
||||||
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
|
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
|
||||||
@@ -41,4 +41,4 @@ People *love* thorough bug reports. I'm not even kidding.
|
|||||||
By contributing, you agree that your contributions will be licensed under its AGPL License.
|
By contributing, you agree that your contributions will be licensed under its AGPL License.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md)
|
This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebookarchive/draft-js/blob/main/CONTRIBUTING.md)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Maintainers Guide
|
# Maintainers Guide
|
||||||
|
|
||||||
This is the start of the maintainers guide for Mealie developers. Those who have been invited to the GitHub organization and/or those who whish to play a bigger part in the Mealie developers community may find this helpful.
|
This is the start of the maintainers guide for Mealie developers. Those who have been invited to the GitHub organization and/or those who wish to play a bigger part in the Mealie developers community may find this helpful.
|
||||||
|
|
||||||
## Managing Issues
|
## Managing Issues
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ After you've reviered an issue it will generally move into one of two states:
|
|||||||
|
|
||||||
|
|
||||||
`needs more info`
|
`needs more info`
|
||||||
: The orignal post does not contain enough information, and if the reporter does not provide additional information, the issue will be automatically closed.
|
: The original post does not contain enough information, and if the reporter does not provide additional information, the issue will be automatically closed.
|
||||||
|
|
||||||
Once you've reviewed an issue and moved it into another category, you should remove the triage label.
|
Once you've reviewed an issue and moved it into another category, you should remove the triage label.
|
||||||
|
|
||||||
@@ -40,18 +40,38 @@ Mealie is published via GitHub actions to the GitHub container registry with the
|
|||||||
: published when a new GitHub Release is created - [Actions File](https://github.com/mealie-recipes/mealie/blob/mealie-next/.github/workflows/release.yml)
|
: published when a new GitHub Release is created - [Actions File](https://github.com/mealie-recipes/mealie/blob/mealie-next/.github/workflows/release.yml)
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
Both the latest, and {version} tags will be the same container on the release of a new version
|
Both the latest, and {version} tags will be the same image on the release of a new version
|
||||||
|
|
||||||
### Process
|
### Process
|
||||||
|
|
||||||
Because we've built all our publishing effors on GitHub Actions we rely primarily on automations to perform our releases. As such creating a new build of Mealie is as simple as creating a new GitHub release. Here are the general steps we take to create a new release
|
Because we've built all our publishing efforts on GitHub Actions we rely primarily on automations to perform our releases. As such creating a new build of Mealie is as simple as creating a new GitHub release. Here are the general steps we take to create a new release
|
||||||
|
|
||||||
1. Navigate to the [Github Release Page](https://github.com/mealie-recipes/mealie/releases) and click the 'Draft a new release' button.
|
1. Navigate to the [Github Release Page](https://github.com/mealie-recipes/mealie/releases) and click the 'Draft a new release' button.
|
||||||
2. Choose a tag and increment the version according to the semver specification. i.e, **major** version for breaking changes, **minor** for feature updates, and **patch** for bug fixes.
|
2. Choose a tag and increment the version according to the semver specification. i.e, **major** version for breaking changes, **minor** for feature updates, and **patch** for bug fixes.
|
||||||
3. Name the Release, usually just the tag is fine, however if there is a special feature you'd like to higlight this would be a great place to do it.
|
3. Name the Release, usually just the tag is fine, however if there is a special feature you'd like to highlight this would be a great place to do it.
|
||||||
4. Click the "Generate release notes" button which will pull in all the Git Commits as a changelog. For bug fix only releases this is sufficient, however if there are major features, or good quality of life improvements it's good to provide those prior to listing the full changelog.
|
4. Click the "Generate release notes" button which will pull in all the Git Commits as a changelog. For bug fix only releases this is sufficient, however if there are major features, or good quality of life improvements it's good to provide those prior to listing the full changelog.
|
||||||
|
|
||||||
!!! tip
|
!!! tip
|
||||||
Don't worry about setting the version number in the container or code, it's set during the build process and uses the tag you specified when drafting a new release.
|
Don't worry about setting the version number in the container or code, it's set during the build process and uses the tag you specified when drafting a new release.
|
||||||
|
|
||||||
You can see how this is done in the [Actions File](https://github.com/mealie-recipes/mealie/blob/mealie-next/.github/workflows/partial-builder.yml#L35-L37)
|
You can see how this is done in the [Actions File](https://github.com/mealie-recipes/mealie/blob/mealie-next/.github/workflows/partial-builder.yml#L35-L37)
|
||||||
|
|
||||||
|
### Tags and Releases
|
||||||
|
|
||||||
|
Mealie tries to adhere to a strict [Semver](https://semver.org/) policy. This means that we try to keep our releases as stable as possible, and only introduce breaking changes when absolutely necessary. As such we try to keep our releases as follows:
|
||||||
|
|
||||||
|
- **Major** releases are reserved for breaking changes, and are not expected to be frequent. Ideally, we will remain at v1.x.x for the forseeable future.
|
||||||
|
- **Minor** releases are reserved for new features, and are expected to be frequent.
|
||||||
|
- **Patch** releases are reserved for bug fixes, and are expected to be frequent.
|
||||||
|
|
||||||
|
Any maintainer who has privileges on GitHub to create a new release can create a release at any time they feel it is necessary. However, it is recommended that you reach out in the discord to other maintainers and get at least one other maintainer to approve the release.
|
||||||
|
|
||||||
|
An important caveat to this is that we _may_ make breaking changes in a minor release if it is security related. In this case, the releaser should headline the release notes with the notice and impact of the breaking change, however we may not bump the major version depending on user impact.
|
||||||
|
|
||||||
|
### Release Notes
|
||||||
|
|
||||||
|
When drafting a new release, GitHub will automatically pull in all the commits since the last release. This is a great start. After pulling in all of the commits, you should add sections for
|
||||||
|
|
||||||
|
- New Features - Any new features that are being introduced in this release (screenshots are great here)
|
||||||
|
- Bug Fixes - Significant bug fixes that are being introduced in this release, smaller bug fixes can be left out if they are noted in a commit message
|
||||||
|
- Breaking Changes - Any breaking changes that are being introduced in this release (should be rare)
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ Prerequisites
|
|||||||
- Visual Studio Code
|
- Visual Studio Code
|
||||||
|
|
||||||
### Linux and MacOS
|
### Linux and MacOS
|
||||||
|
|
||||||
First ensure that docker is running. Then when you clone the repo and open with VS Code you should see a popup asking you to reopen the project inside a development container. Click yes and it will build the development container and run the setup required to run both the backend API and the frontend webserver. This also pre-configures pre-commit hooks to ensure that the code is up to date before committing.
|
First ensure that docker is running. Then when you clone the repo and open with VS Code you should see a popup asking you to reopen the project inside a development container. Click yes and it will build the development container and run the setup required to run both the backend API and the frontend webserver. This also pre-configures pre-commit hooks to ensure that the code is up to date before committing.
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
Make sure the VSCode Dev Containers extension is installed, then select "Dev Containers: Clone Repository in Container Volume..." in the command pallete (F1). Select your forked repo and choose the `mealie-next` branch, which contains the latest changes. This mounts your repository directly in WSL2, which [greatly improves the performance of the container](https://code.visualstudio.com/docs/devcontainers/containers#_quick-start-open-a-git-repository-or-github-pr-in-an-isolated-container-volume), and enables hot-reloading for the frontend. Running the container on a mounted volume may not work correctly on Windows due to WSL permission mapping issues.
|
|
||||||
|
|
||||||
[Checkout the makefile reference](#make-file-reference) for all of the available commands.
|
Make sure the VSCode Dev Containers extension is installed, then select "Dev Containers: Clone Repository in Container Volume..." in the command palette (F1). Select your forked repo and choose the `mealie-next` branch, which contains the latest changes. This mounts your repository directly in WSL2, which [greatly improves the performance of the container](https://code.visualstudio.com/docs/devcontainers/containers#_quick-start-open-a-git-repository-or-github-pr-in-an-isolated-container-volume), and enables hot-reloading for the frontend. Running the container on a mounted volume may not work correctly on Windows due to WSL permission mapping issues.
|
||||||
|
|
||||||
!!! tip
|
!!! tip
|
||||||
For slow terminal checkout the solution in this [GitHub Issue](https://github.com/microsoft/vscode/issues/133215)
|
For slow terminal checkout the solution in this [GitHub Issue](https://github.com/microsoft/vscode/issues/133215)
|
||||||
@@ -29,16 +29,18 @@ Make sure the VSCode Dev Containers extension is installed, then select "Dev Con
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Without Dev Containers
|
## Without Dev Containers
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- [Python 3.10](https://www.python.org/downloads/)
|
- [Python 3.10](https://www.python.org/downloads/)
|
||||||
- [Poetry](https://python-poetry.org/docs/#installation)
|
- [Poetry](https://python-poetry.org/docs/#installation)
|
||||||
- [Node v16.x](https://nodejs.org/en/)
|
- [Node v16.x](https://nodejs.org/en/)
|
||||||
- [yarn](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable)
|
- [yarn](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable)
|
||||||
|
- [task](https://taskfile.dev/#/installation)
|
||||||
|
|
||||||
### Installing Dependencies
|
### Installing Dependencies
|
||||||
|
|
||||||
Once the prerequisites are installed you can cd into the project base directory and run `make setup` to install the python and node dependencies.
|
Once the prerequisites are installed you can cd into the project base directory and run `task setup` to install the python and node dependencies, and download the NLP model.
|
||||||
|
|
||||||
=== "Linux / macOS"
|
=== "Linux / macOS"
|
||||||
|
|
||||||
@@ -46,29 +48,16 @@ Once the prerequisites are installed you can cd into the project base directory
|
|||||||
# Naviate To The Root Directory
|
# Naviate To The Root Directory
|
||||||
cd /path/to/project
|
cd /path/to/project
|
||||||
|
|
||||||
# Utilize the Makefile to Install Dependencies
|
# Utilize the Taskfile to Install Dependencies
|
||||||
make setup
|
task setup
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Windows"
|
|
||||||
|
|
||||||
``` powershell
|
|
||||||
# Install Python Dependencies
|
|
||||||
Set-Directory -Path "C:\path\to\project"
|
|
||||||
poetry install
|
|
||||||
|
|
||||||
# Install Node Dependencies
|
|
||||||
Set-Directory frontend
|
|
||||||
yarn install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Setting ENV Variables
|
|
||||||
|
|
||||||
Before you start the server you MUST copy the `template.env` and `frontend/template.env` files to their respective locations with the name `.env` and `frontend/.env` respectively. The application will-not run without these files.
|
|
||||||
|
|
||||||
## Postgres
|
## Postgres
|
||||||
- Whether using a container or manual install, you need to set up your own postgres dev server. The database, username, password, etc should match the `POSTGRES_*` options located in the `.env` file.
|
|
||||||
- Install psycog2 with `poetry install -E pgsql` (in the main `mealie` directory, *not* `frontend`)
|
The taskfile has two commands that need to be run to run the development environment against a postgres database.
|
||||||
|
|
||||||
|
- `task dev:services` - This will start the postgres database, and a smtp server for email testing.
|
||||||
|
- `task py:postgres` - This will run that backend API configured for the local postgres database.
|
||||||
|
|
||||||
## Starting The Server
|
## Starting The Server
|
||||||
|
|
||||||
@@ -78,57 +67,24 @@ Now you're ready to start the servers. You'll need two shells open, One for the
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Terminal #1
|
# Terminal #1
|
||||||
make backend
|
task py
|
||||||
|
|
||||||
# Terminal #2
|
# Terminal #2
|
||||||
make frontend
|
task ui
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Windows"
|
|
||||||
|
|
||||||
``` powershell
|
|
||||||
# Terminal # 1
|
|
||||||
poetry run python mealie/db/init_db.py # Initialize the database
|
|
||||||
poetry run python mealie/app.py # start application
|
|
||||||
|
|
||||||
# Terminal # 2
|
|
||||||
Set-Directory frontend
|
|
||||||
yarn run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Make File Reference
|
|
||||||
|
|
||||||
Run `make help` for reference. If you're on a system that doesn't support makefiles in most cases you can use the commands directly in your terminal by copy/pasting them from the Makefile.
|
|
||||||
|
|
||||||
```
|
|
||||||
docs 📄 Start Mkdocs Development Server
|
|
||||||
code-gen 🤖 Run Code-Gen Scripts
|
|
||||||
setup 🏗 Setup Development Instance
|
|
||||||
setup-model 🤖 Get the latest NLP CRF++ Model
|
|
||||||
clean-data ⚠️ Removes All Developer Data for a fresh server start
|
|
||||||
clean-pyc 🧹 Remove Python file artifacts
|
|
||||||
clean-test 🧹 Remove test and coverage artifacts
|
|
||||||
backend-clean 🧹 Remove all build, test, coverage and Python artifacts
|
|
||||||
backend-test 🧪 Run tests quickly with the default Python
|
|
||||||
backend-format 🧺 Format, Check and Flake8
|
|
||||||
backend-all 🧪 Runs all the backend checks and tests
|
|
||||||
backend-coverage ☂️ Check code coverage quickly with the default Python
|
|
||||||
backend 🎬 Start Mealie Backend Development Server
|
|
||||||
frontend 🎬 Start Mealie Frontend Development Server
|
|
||||||
frontend-build 🏗 Build Frontend in frontend/dist
|
|
||||||
frontend-generate 🏗 Generate Code for Frontend
|
|
||||||
frontend-lint 🧺 Run yarn lint
|
|
||||||
docker-dev 🐳 Build and Start Docker Development Stack (currently not functional, see #756, #1072)
|
|
||||||
docker-prod 🐳 Build and Start Docker Production Stack
|
|
||||||
|
|
||||||
```
|
|
||||||
## Internationalization
|
## Internationalization
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
|
|
||||||
We use vue-i18n package for internationalization. Translations are stored in json format located in [frontend/lang/messages](https://github.com/mealie-recipes/mealie/tree/mealie-next/frontend/lang/messages).
|
We use vue-i18n package for internationalization. Translations are stored in json format located in [frontend/lang/messages](https://github.com/mealie-recipes/mealie/tree/mealie-next/frontend/lang/messages).
|
||||||
|
|
||||||
### Backend
|
### Backend
|
||||||
|
|
||||||
Translations are stored in json format located in [mealie/lang/messages](https://github.com/mealie-recipes/mealie/tree/mealie-next/mealie/lang/messages).
|
Translations are stored in json format located in [mealie/lang/messages](https://github.com/mealie-recipes/mealie/tree/mealie-next/mealie/lang/messages).
|
||||||
|
|
||||||
### Quick frontend localization with VS Code
|
### Quick frontend localization with VS Code
|
||||||
|
|
||||||
[i18n Ally for VScode](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally) is helpful for generating new strings to translate using Code Actions. It also has a nice feature, which shows translations in-place when editing code.
|
[i18n Ally for VScode](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally) is helpful for generating new strings to translate using Code Actions. It also has a nice feature, which shows translations in-place when editing code.
|
||||||
|
|
||||||
A few settings must be tweaked to make the most of its features. Some settings are stored on project level, but most of them have to be set manually in your workspace or user settings.\
|
A few settings must be tweaked to make the most of its features. Some settings are stored on project level, but most of them have to be set manually in your workspace or user settings.\
|
||||||
|
|||||||
@@ -3,37 +3,80 @@
|
|||||||
!!! info
|
!!! info
|
||||||
This guide was submitted by a community member. Find something wrong? Submit a PR to get it fixed!
|
This guide was submitted by a community member. Find something wrong? Submit a PR to get it fixed!
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
User [brasilikum](https://github.com/brasilikum) opened an issue on the main repo about how they had created an [iOS shortcut](https://github.com/mealie-recipes/mealie/issues/103) for interested users.
|
|
||||||
This original method broke after the transition to version 1.X and an issue was raised on [Github](https://github.com/mealie-recipes/mealie/issues/2092) GitHub user [Zippyy](https://github.com/zippyy) has helped to create a working shortcut for version 1.X.
|
|
||||||
|
|
||||||
This is a useful utility for iOS users who browse for recipes in their web browser from their devices.
|
|
||||||
|
|
||||||
Don't know what an iOS shortcut is? Neither did I! Experienced iOS users may already be familiar with this utility but for the uninitiated, here is the official Apple explanation:
|
Don't know what an iOS shortcut is? Neither did I! Experienced iOS users may already be familiar with this utility but for the uninitiated, here is the official Apple explanation:
|
||||||
|
|
||||||
> A shortcut is a quick way to get one or more tasks done with your apps. The Shortcuts app lets you create your own shortcuts with multiple steps. For example, build a “Surf Time” shortcut that grabs the surf report, gives an ETA to the beach, and launches your surf music playlist.
|
> A shortcut is a quick way to get one or more tasks done with your apps. The Shortcuts app lets you create your own shortcuts with multiple steps. For example, build a “Surf Time” shortcut that grabs the surf report, gives an ETA to the beach, and launches your surf music playlist.
|
||||||
|
|
||||||
Basically it is a visual scripting language that lets a user build an automation in a guided fashion. The automation can be [shared with anyone](https://www.icloud.com/shortcuts/cc568d1615bc4f998789f85d1ef74846) but if it is a user creation, you'll have to jump through a few hoops to make an untrusted automation work on your device.
|
Basically it is a visual scripting language that lets a user build an automation in a guided fashion. The automation can be [shared with anyone](https://www.icloud.com/shortcuts/94aa272af5ff4d2c8fe5e13a946f89a9) but if it is a user creation, you'll have to jump through a few hoops to make an untrusted automation work on your device.
|
||||||
|
|
||||||
This guide assumes that you already know how to [generate API tokens](https://hay-kot.github.io/mealie/documentation/users-groups/user-settings/#api-key-generation) for your user that intends to use an iOS shortcut.
|
## Setup Video
|
||||||
|
|
||||||
First, click the [link](https://www.icloud.com/shortcuts/cc568d1615bc4f998789f85d1ef74846) and begin the setup of the shortcut.
|
The following YouTube video walks through setting up the shortcut in 3 minutes for those who prefer following along visually.
|
||||||
|
|
||||||
|
<iframe width="560" height="315" src="https://www.youtube.com/embed/XZk6S1MVUrE?si=HGH07RbK-Ip_1qFz" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
|
||||||

|
## Guide
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
Before setting up the shortcut, make sure you have the following information ready and easily accessable on your Apple device.
|
||||||
|
|
||||||
|
1. The URL of your Mealie instance
|
||||||
|
2. An API Key for your user
|
||||||
|
3. A Gemini API Key from [Google AI Studio](https://makersuite.google.com)
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
A Gemini API Key is not required for importing URLs from Safari or your Camera, however you will not be able to take a photo of a recipe and import it without a Gemini key.
|
||||||
|
|
||||||
|
Google AI Studio is currently only available in [certain countries and languages](https://ai.google.dev/available_regions). Most notably it is not currently available in Europe.
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
On the Apple device you wish to add the shortcut to, click on [this link](https://www.icloud.com/shortcuts/94aa272af5ff4d2c8fe5e13a946f89a9) to begin the setup of the shortcut.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Next, you need to replace `url` and `port` with the information for your Mealie instance.
|
Next, you need to replace `url` and `port` with the information for your Mealie instance.
|
||||||
|
|
||||||
If you have a domain that you use (e.g. `https://mealie.example.com`), put that here. If you just run local, then you need to put in your Mealie instance IP and the port you use (e.g. the default is `9925`).
|
If you have a domain that you use (e.g. `https://mealie.example.com`), put that here. If you just run local, then you need to put in your Mealie instance IP and the port you use (e.g. the default is `9925`).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||

|
Next, you need to replace `MEALIE_API_KEY` with your API token.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Finally, you need to replace the word `keyhere` with your API token. Keep the word `Bearer`!!!
|
Finally, replace `GEMINI_API_KEY` with the one you got from [Google AI Studio](https://makersuite.google.com)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||

|
You may wish to [add the shortcut to your home screen](https://support.apple.com/guide/shortcuts/add-a-shortcut-to-the-home-screen-apd735880972/ios) for easier access.
|
||||||
|
|
||||||
You should now be able to share a website to the shortcut and have Mealie grab all the necessary information!
|
## Features
|
||||||
|
|
||||||
|
- Share a website from Safari with Mealie to import via URL.
|
||||||
|
- Share a recipe photo from photos to perform OCR and import a physical recipe.
|
||||||
|
- Trigger the shortcut and take a photo of a physical recipe to import.
|
||||||
|
- Trigger the shortcut to select a photo from your Photos app to import.
|
||||||
|
- Trigger the shortcut to take a picture of a URL (like on the bottom of a printed recipe) to import.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
Sometimes Gemini will not be able to parse a recipe, and you will get an error. Users have found success with a combination of the following:
|
||||||
|
|
||||||
|
1. #### Try Again
|
||||||
|
Sometimes Gemini returns the wrong information which causes the import to fail. Often, trying again will be successful.
|
||||||
|
|
||||||
|
2. #### Photo Quality
|
||||||
|
Make sure there is no large glare or shadow over the picture, and you have all the text in frame.
|
||||||
|
|
||||||
|
3. #### Edit the Photo
|
||||||
|
Users have found success by cropping the picture to just the recipe card, adding a "mono" filter, and cranking up the exposure before importing.
|
||||||
|
|
||||||
|
## History
|
||||||
|
|
||||||
|
User [brasilikum](https://github.com/brasilikum) opened an issue on the main repo about how they had created an [iOS shortcut](https://github.com/mealie-recipes/mealie/issues/103) for interested users.
|
||||||
|
|
||||||
|
This original method broke after the transition to version 1.X and an issue was raised on [Github](https://github.com/mealie-recipes/mealie/issues/2092) GitHub user [Zippyy](https://github.com/zippyy) has helped to create a working shortcut for version 1.X.
|
||||||
|
|
||||||
|
When OCR was removed from Mealie, GitHub user [hunterjm](https://github.com/zippyy) created a new shortcut that uses Apple's built-in OCR and Google Gemini to enhance and replace that functionality.
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ Changing the webworker settings may cause unforeseen memory leak issues with Mea
|
|||||||
| ---------------- | :-----: | --------------------------------------------------------------------------------------------------------------------------------- |
|
| ---------------- | :-----: | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| WEB_GUNICORN | false | Enables Gunicorn to manage Uvicorn web for multiple works |
|
| WEB_GUNICORN | false | Enables Gunicorn to manage Uvicorn web for multiple works |
|
||||||
| WORKERS_PER_CORE | 1 | Set the number of workers to the number of CPU cores multiplied by this value (Value \* CPUs). More info [here][workers_per_core] |
|
| WORKERS_PER_CORE | 1 | Set the number of workers to the number of CPU cores multiplied by this value (Value \* CPUs). More info [here][workers_per_core] |
|
||||||
| MAX_WORKERS | 1 | Set the maximum number of workers to use. Default is not set meaning unlimited. More info [here][max_workers] |
|
| MAX_WORKERS | None | Set the maximum number of workers to use. Default is not set meaning unlimited. More info [here][max_workers] |
|
||||||
| WEB_CONCURRENCY | 1 | Override the automatic definition of number of workers. More info [here][web_concurrency] |
|
| WEB_CONCURRENCY | 2 | Override the automatic definition of number of workers. More info [here][web_concurrency] |
|
||||||
|
|
||||||
### LDAP
|
### LDAP
|
||||||
|
|
||||||
@@ -95,3 +95,8 @@ Setting the following environmental variables will change the theme of the front
|
|||||||
| THEME_DARK_INFO | #1976D2 | Dark Theme Config Variable |
|
| THEME_DARK_INFO | #1976D2 | Dark Theme Config Variable |
|
||||||
| THEME_DARK_WARNING | #FF6D00 | Dark Theme Config Variable |
|
| THEME_DARK_WARNING | #FF6D00 | Dark Theme Config Variable |
|
||||||
| THEME_DARK_ERROR | #EF5350 | Dark Theme Config Variable |
|
| THEME_DARK_ERROR | #EF5350 | Dark Theme Config Variable |
|
||||||
|
|
||||||
|
|
||||||
|
[workers_per_core]: https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/2daa3e3873c837d5781feb4ff6a40a89f791f81b/README.md#workers_per_core
|
||||||
|
[max_workers]: https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/2daa3e3873c837d5781feb4ff6a40a89f791f81b/README.md#max_workers
|
||||||
|
[web_concurrency]: https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/2daa3e3873c837d5781feb4ff6a40a89f791f81b/README.md#web_concurrency
|
||||||
|
|||||||
@@ -113,8 +113,6 @@ We also provide versioned containers that allow to pin to a specific release. Ea
|
|||||||
|
|
||||||
`ghcr.io/mealie-recipes/mealie:latest`
|
`ghcr.io/mealie-recipes/mealie:latest`
|
||||||
|
|
||||||
_Note: This tag is not yet available; it will be available with the v1 stable release_
|
|
||||||
|
|
||||||
The latest tag provides the latest released image of Mealie.
|
The latest tag provides the latest released image of Mealie.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ PostgreSQL might be considered if you need to support many concurrent users. In
|
|||||||
version: "3.7"
|
version: "3.7"
|
||||||
services:
|
services:
|
||||||
mealie:
|
mealie:
|
||||||
image: ghcr.io/mealie-recipes/mealie:v1.0.0-RC2
|
image: ghcr.io/mealie-recipes/mealie:v1.3.1 # (3)
|
||||||
container_name: mealie
|
container_name: mealie
|
||||||
ports:
|
ports:
|
||||||
- "9925:9000"
|
- "9925:9000" # (1)
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: 1000M # (1)
|
memory: 1000M # (2)
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
volumes:
|
volumes:
|
||||||
@@ -60,3 +60,4 @@ volumes:
|
|||||||
|
|
||||||
1. To access the mealie interface you only need to expose port 9000 on the mealie container. Here we expose port 9925 on the host, but feel free to change this to any port you like.
|
1. To access the mealie interface you only need to expose port 9000 on the mealie container. Here we expose port 9925 on the host, but feel free to change this to any port you like.
|
||||||
2. Setting an explicit memory limit is recommended. Python can pre-allocate larger amounts of memory than is necessary if you have a machine with a lot of RAM. This can cause the container to idle at a high memory usage. Setting a memory limit will improve idle performance.
|
2. Setting an explicit memory limit is recommended. Python can pre-allocate larger amounts of memory than is necessary if you have a machine with a lot of RAM. This can cause the container to idle at a high memory usage. Setting a memory limit will improve idle performance.
|
||||||
|
3. You should double check this value isn't out of date when setting up for the first time; check the README and use the value from the "latest release" badge at the top - the format should be `vX.Y.Z`. Whilst a 'latest' tag is available, the Mealie team advises specifying a specific version tag and consciously updating to newer versions when you have time to read the release notes and ensure you follow any manual actions required (which should be rare).
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Installing with SQLite
|
# Installing with SQLite
|
||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
If you're plannin on deploying and using Network Attached Storage with Mealie, you should use [Postgres](./postgres.md) instead of SQLite. SQLite is not designed to be used with Network Attached Storage and can cause data corruption, or locked database errors
|
If you're planning on deploying and using Network Attached Storage with Mealie, you should use [Postgres](./postgres.md) instead of SQLite. SQLite is not designed to be used with Network Attached Storage and can cause data corruption, or locked database errors
|
||||||
|
|
||||||
|
|
||||||
SQLite is a popular, open source, self-contained, zero-configuration database that is the ideal choice for Mealie when you have 1-20 Users. Below is a ready to use docker-compose.yaml file for deploying Mealie on your server.
|
SQLite is a popular, open source, self-contained, zero-configuration database that is the ideal choice for Mealie when you have 1-20 Users. Below is a ready to use docker-compose.yaml file for deploying Mealie on your server.
|
||||||
@@ -13,7 +13,7 @@ SQLite is a popular, open source, self-contained, zero-configuration database th
|
|||||||
version: "3.7"
|
version: "3.7"
|
||||||
services:
|
services:
|
||||||
mealie:
|
mealie:
|
||||||
image: ghcr.io/mealie-recipes/mealie:v1.0.0-RC2
|
image: ghcr.io/mealie-recipes/mealie:v1.3.1 # (3)
|
||||||
container_name: mealie
|
container_name: mealie
|
||||||
ports:
|
ports:
|
||||||
- "9925:9000" # (1)
|
- "9925:9000" # (1)
|
||||||
@@ -43,3 +43,4 @@ volumes:
|
|||||||
|
|
||||||
1. To access the mealie interface you only need to expose port 9000 on the container. Here we expose port 9925 on the host, but feel free to change this to any port you like.
|
1. To access the mealie interface you only need to expose port 9000 on the container. Here we expose port 9925 on the host, but feel free to change this to any port you like.
|
||||||
2. Setting an explicit memory limit is recommended. Python can pre-allocate larger amounts of memory than is necessary if you have a machine with a lot of RAM. This can cause the container to idle at a high memory usage. Setting a memory limit will improve idle performance.
|
2. Setting an explicit memory limit is recommended. Python can pre-allocate larger amounts of memory than is necessary if you have a machine with a lot of RAM. This can cause the container to idle at a high memory usage. Setting a memory limit will improve idle performance.
|
||||||
|
3. You should double check this value isn't out of date when setting up for the first time; check the README and use the value from the "latest release" badge at the top - the format should be `vX.Y.Z`. Whilst a 'latest' tag is available, the Mealie team advises specifying a specific version tag and consciously updating to newer versions when you have time to read the release notes and ensure you follow any manual actions required (which should be rare).
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
# About The Project
|
# About The Project
|
||||||
|
|
||||||
!!! warning "Mealie v1 Beta Release"
|
|
||||||
|
|
||||||
This documentation is for the Mealie v1 Beta release and is not final. As such, it may contain incomplete or incorrect information. You should understand that installing Mealie v1 Beta is a work in progress and while we've committed to maintaining the database schema and provided migrations, we are still in the process of adding new features, and robust testing to ensure the application works as expected.
|
|
||||||
|
|
||||||
You should likely find bugs, errors, and unfinished pages within the application. To find the current status of the release you can checkout the [project on github](https://github.com/mealie-recipes/mealie/projects/7) or reach out on discord.
|
|
||||||
|
|
||||||
Mealie is a self hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend application built in Vue for a pleasant user experience for the whole family. Easily add recipes into your database by providing the url and Mealie will automatically import the relevant data or add a family recipe with the UI editor. Mealie also provides an API for interactions from 3rd party applications.
|
Mealie is a self hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend application built in Vue for a pleasant user experience for the whole family. Easily add recipes into your database by providing the url and Mealie will automatically import the relevant data or add a family recipe with the UI editor. Mealie also provides an API for interactions from 3rd party applications.
|
||||||
|
|
||||||
[Remember to join the Discord](https://discord.gg/QuStdQGSGK)
|
[Remember to join the Discord](https://discord.gg/QuStdQGSGK)
|
||||||
|
|||||||
@@ -20,5 +20,6 @@ If you are upgrading from pre-v1.0.0 to v1.0.0, make sure you read [Migrating to
|
|||||||
For all setups using Docker the updating process looks something like this
|
For all setups using Docker the updating process looks something like this
|
||||||
|
|
||||||
- Stop the container using `docker compose down`
|
- Stop the container using `docker compose down`
|
||||||
|
- If you are not using the latest tag, change the version (image tag) in your docker-compose file
|
||||||
- Pull the latest image using `docker compose pull`
|
- Pull the latest image using `docker compose pull`
|
||||||
- Start the container again using `docker compose up -d`
|
- Start the container again using `docker compose up -d`
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ extra_css:
|
|||||||
- assets/stylesheets/custom.css
|
- assets/stylesheets/custom.css
|
||||||
extra_javascript:
|
extra_javascript:
|
||||||
- assets/js/extra.js
|
- assets/js/extra.js
|
||||||
repo_url: https://github.com/hay-kot/mealie/
|
repo_url: https://github.com/mealie-recipes/mealie/
|
||||||
repo_name: hay-kot/mealie
|
repo_name: mealie-recipes/mealie
|
||||||
edit_uri: edit/mealie-next/docs/docs/
|
edit_uri: edit/mealie-next/docs/docs/
|
||||||
|
|
||||||
nav:
|
nav:
|
||||||
|
|||||||
55
frontend/components/Domain/Cookbook/CookbookEditor.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-card-text v-if="cookbook">
|
||||||
|
<v-text-field v-model="cookbook.name" :label="$t('cookbook.cookbook-name')"></v-text-field>
|
||||||
|
<v-textarea v-model="cookbook.description" auto-grow :rows="2" :label="$t('recipe.description')"></v-textarea>
|
||||||
|
<RecipeOrganizerSelector v-model="cookbook.categories" selector-type="categories" />
|
||||||
|
<RecipeOrganizerSelector v-model="cookbook.tags" selector-type="tags" />
|
||||||
|
<RecipeOrganizerSelector v-model="cookbook.tools" selector-type="tools" />
|
||||||
|
<v-switch v-model="cookbook.public" hide-details single-line>
|
||||||
|
<template #label>
|
||||||
|
{{ $t('cookbook.public-cookbook') }}
|
||||||
|
<HelpIcon small right class="ml-2">
|
||||||
|
{{ $t('cookbook.public-cookbook-description') }}
|
||||||
|
</HelpIcon>
|
||||||
|
</template>
|
||||||
|
</v-switch>
|
||||||
|
<div class="mt-4">
|
||||||
|
<h3 class="text-subtitle-1 d-flex align-center mb-0 pb-0">
|
||||||
|
{{ $t('cookbook.filter-options') }}
|
||||||
|
<HelpIcon right small class="ml-2">
|
||||||
|
{{ $t('cookbook.filter-options-description') }}
|
||||||
|
</HelpIcon>
|
||||||
|
</h3>
|
||||||
|
<v-switch v-model="cookbook.requireAllCategories" class="mt-0" hide-details single-line>
|
||||||
|
<template #label> {{ $t('cookbook.require-all-categories') }} </template>
|
||||||
|
</v-switch>
|
||||||
|
<v-switch v-model="cookbook.requireAllTags" hide-details single-line>
|
||||||
|
<template #label> {{ $t('cookbook.require-all-tags') }} </template>
|
||||||
|
</v-switch>
|
||||||
|
<v-switch v-model="cookbook.requireAllTools" hide-details single-line>
|
||||||
|
<template #label> {{ $t('cookbook.require-all-tools') }} </template>
|
||||||
|
</v-switch>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
import { ReadCookBook } from "~/lib/api/types/cookbook";
|
||||||
|
import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
|
||||||
|
export default defineComponent({
|
||||||
|
components: { RecipeOrganizerSelector },
|
||||||
|
props: {
|
||||||
|
cookbook: {
|
||||||
|
type: Object as () => ReadCookBook,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
type: Object as () => any,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -107,7 +107,7 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function getShoppingLists() {
|
async function getShoppingLists() {
|
||||||
const { data } = await api.shopping.lists.getAll();
|
const { data } = await api.shopping.lists.getAll(1, -1, { orderBy: "name", orderDirection: "asc" });
|
||||||
if (data) {
|
if (data) {
|
||||||
shoppingLists.value = data.items ?? [];
|
shoppingLists.value = data.items ?? [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
<v-hover v-slot="{ hover }" :open-delay="50">
|
<v-hover v-slot="{ hover }" :open-delay="50">
|
||||||
<v-card
|
<v-card
|
||||||
:class="{ 'on-hover': hover }"
|
:class="{ 'on-hover': hover }"
|
||||||
|
:style="{ cursor }"
|
||||||
:elevation="hover ? 12 : 2"
|
:elevation="hover ? 12 : 2"
|
||||||
:to="route ? recipeRoute : ''"
|
:to="recipeRoute"
|
||||||
:min-height="imageHeight + 75"
|
:min-height="imageHeight + 75"
|
||||||
@click="$emit('click')"
|
@click="$emit('click')"
|
||||||
>
|
>
|
||||||
@@ -33,7 +34,7 @@
|
|||||||
</v-card-title>
|
</v-card-title>
|
||||||
|
|
||||||
<slot name="actions">
|
<slot name="actions">
|
||||||
<v-card-actions class="px-1">
|
<v-card-actions v-if="showRecipeContent" class="px-1">
|
||||||
<RecipeFavoriteBadge v-if="isOwnGroup" class="absolute" :slug="slug" show-always />
|
<RecipeFavoriteBadge v-if="isOwnGroup" class="absolute" :slug="slug" show-always />
|
||||||
|
|
||||||
<RecipeRating class="pb-1" :value="rating" :name="name" :slug="slug" :small="true" />
|
<RecipeRating class="pb-1" :value="rating" :name="name" :slug="slug" :small="true" />
|
||||||
@@ -101,10 +102,6 @@ export default defineComponent({
|
|||||||
required: false,
|
required: false,
|
||||||
default: "abc123",
|
default: "abc123",
|
||||||
},
|
},
|
||||||
route: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
tags: {
|
tags: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
@@ -123,14 +120,18 @@ export default defineComponent({
|
|||||||
const { isOwnGroup } = useLoggedInState();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "")
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
const showRecipeContent = computed(() => props.recipeId && props.slug);
|
||||||
const recipeRoute = computed<string>(() => {
|
const recipeRoute = computed<string>(() => {
|
||||||
return `/g/${groupSlug.value}/r/${props.slug}`;
|
return showRecipeContent.value ? `/g/${groupSlug.value}/r/${props.slug}` : "";
|
||||||
});
|
});
|
||||||
|
const cursor = computed(() => showRecipeContent.value ? "pointer" : "auto");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isOwnGroup,
|
isOwnGroup,
|
||||||
recipeRoute,
|
recipeRoute,
|
||||||
|
showRecipeContent,
|
||||||
|
cursor,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<v-card
|
<v-card
|
||||||
:ripple="false"
|
:ripple="false"
|
||||||
:class="isFlat ? 'mx-auto flat' : 'mx-auto'"
|
:class="isFlat ? 'mx-auto flat' : 'mx-auto'"
|
||||||
|
:style="{ cursor }"
|
||||||
hover
|
hover
|
||||||
:to="$listeners.selected ? undefined : recipeRoute"
|
:to="$listeners.selected ? undefined : recipeRoute"
|
||||||
@click="$emit('selected')"
|
@click="$emit('selected')"
|
||||||
@@ -37,8 +38,9 @@
|
|||||||
</v-list-item-subtitle>
|
</v-list-item-subtitle>
|
||||||
<div class="d-flex flex-wrap justify-end align-center">
|
<div class="d-flex flex-wrap justify-end align-center">
|
||||||
<slot name="actions">
|
<slot name="actions">
|
||||||
<RecipeFavoriteBadge v-if="isOwnGroup" :slug="slug" show-always />
|
<RecipeFavoriteBadge v-if="isOwnGroup && showRecipeContent" :slug="slug" show-always />
|
||||||
<v-rating
|
<v-rating
|
||||||
|
v-if="showRecipeContent"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
:class="isOwnGroup ? 'ml-auto' : 'ml-auto pb-2'"
|
:class="isOwnGroup ? 'ml-auto' : 'ml-auto pb-2'"
|
||||||
background-color="secondary lighten-3"
|
background-color="secondary lighten-3"
|
||||||
@@ -52,7 +54,7 @@
|
|||||||
<!-- If we're not logged-in, no items display, so we hide this menu -->
|
<!-- If we're not logged-in, no items display, so we hide this menu -->
|
||||||
<!-- We also add padding to the v-rating above to compensate -->
|
<!-- We also add padding to the v-rating above to compensate -->
|
||||||
<RecipeContextMenu
|
<RecipeContextMenu
|
||||||
v-if="isOwnGroup"
|
v-if="isOwnGroup && showRecipeContent"
|
||||||
:slug="slug"
|
:slug="slug"
|
||||||
:menu-icon="$globals.icons.dotsHorizontal"
|
:menu-icon="$globals.icons.dotsHorizontal"
|
||||||
:name="name"
|
:name="name"
|
||||||
@@ -113,10 +115,6 @@ export default defineComponent({
|
|||||||
required: false,
|
required: false,
|
||||||
default: "abc123",
|
default: "abc123",
|
||||||
},
|
},
|
||||||
route: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
recipeId: {
|
recipeId: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -135,14 +133,19 @@ export default defineComponent({
|
|||||||
const { isOwnGroup } = useLoggedInState();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "")
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
const showRecipeContent = computed(() => props.recipeId && props.slug);
|
||||||
const recipeRoute = computed<string>(() => {
|
const recipeRoute = computed<string>(() => {
|
||||||
return `/g/${groupSlug.value}/r/${props.slug}`;
|
return showRecipeContent.value ? `/g/${groupSlug.value}/r/${props.slug}` : "";
|
||||||
});
|
});
|
||||||
|
const cursor = computed(() => showRecipeContent.value ? "pointer" : "auto");
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isOwnGroup,
|
isOwnGroup,
|
||||||
recipeRoute,
|
recipeRoute,
|
||||||
|
showRecipeContent,
|
||||||
|
cursor,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ export default defineComponent({
|
|||||||
const recipeRefWithScale = computed(() => recipeRef.value ? { scale: props.recipeScale, ...recipeRef.value } : undefined);
|
const recipeRefWithScale = computed(() => recipeRef.value ? { scale: props.recipeScale, ...recipeRef.value } : undefined);
|
||||||
|
|
||||||
async function getShoppingLists() {
|
async function getShoppingLists() {
|
||||||
const { data } = await api.shopping.lists.getAll();
|
const { data } = await api.shopping.lists.getAll(1, -1, { orderBy: "name", orderDirection: "asc" });
|
||||||
if (data) {
|
if (data) {
|
||||||
shoppingLists.value = data.items ?? [];
|
shoppingLists.value = data.items ?? [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<BaseDialog v-if="shoppingListDialog" v-model="dialog" :title="$t('recipe.add-to-list')" :icon="$globals.icons.cartCheck">
|
<BaseDialog v-if="shoppingListDialog" v-model="dialog" :title="$t('recipe.add-to-list')" :icon="$globals.icons.cartCheck">
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-card
|
<v-card
|
||||||
v-for="list in shoppingLists"
|
v-for="list in shoppingListChoices"
|
||||||
:key="list.id"
|
:key="list.id"
|
||||||
hover
|
hover
|
||||||
class="my-2 left-border"
|
class="my-2 left-border"
|
||||||
@@ -14,6 +14,18 @@
|
|||||||
</v-card-title>
|
</v-card-title>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
<template #card-actions>
|
||||||
|
<v-btn
|
||||||
|
text
|
||||||
|
color="grey"
|
||||||
|
@click="dialog = false"
|
||||||
|
>
|
||||||
|
{{ $t("general.cancel") }}
|
||||||
|
</v-btn>
|
||||||
|
<div class="d-flex justify-end" style="width: 100%;">
|
||||||
|
<v-checkbox v-model="preferences.viewAllLists" hide-details :label="$tc('general.show-all')" class="my-auto mr-4" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
v-if="shoppingListIngredientDialog"
|
v-if="shoppingListIngredientDialog"
|
||||||
@@ -26,40 +38,53 @@
|
|||||||
>
|
>
|
||||||
<div style="max-height: 70vh; overflow-y: auto">
|
<div style="max-height: 70vh; overflow-y: auto">
|
||||||
<v-card
|
<v-card
|
||||||
v-for="(section, sectionIndex) in recipeIngredientSections" :key="section.recipeId + sectionIndex"
|
v-for="(recipeSection, recipeSectionIndex) in recipeIngredientSections" :key="recipeSection.recipeId + recipeSectionIndex"
|
||||||
elevation="0"
|
elevation="0"
|
||||||
height="fit-content"
|
height="fit-content"
|
||||||
width="100%"
|
width="100%"
|
||||||
>
|
>
|
||||||
<v-divider v-if="sectionIndex > 0" class="mt-3" />
|
<v-divider v-if="recipeSectionIndex > 0" class="mt-3" />
|
||||||
<v-card-title
|
<v-card-title
|
||||||
v-if="recipeIngredientSections.length > 1"
|
v-if="recipeIngredientSections.length > 1"
|
||||||
class="justify-center"
|
class="justify-center text-h5"
|
||||||
width="100%"
|
width="100%"
|
||||||
>
|
>
|
||||||
<v-container style="width: 100%;">
|
<v-container style="width: 100%;">
|
||||||
<v-row no-gutters class="ma-0 pa-0">
|
<v-row no-gutters class="ma-0 pa-0">
|
||||||
<v-col cols="12" align-self="center" class="text-center">
|
<v-col cols="12" align-self="center" class="text-center">
|
||||||
{{ section.recipeName }}
|
{{ recipeSection.recipeName }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="section.recipeScale > 1" no-gutters class="ma-0 pa-0">
|
<v-row v-if="recipeSection.recipeScale > 1" no-gutters class="ma-0 pa-0">
|
||||||
<!-- TODO: make this editable in the dialog and visible on single-recipe lists -->
|
<!-- TODO: make this editable in the dialog and visible on single-recipe lists -->
|
||||||
<v-col cols="12" align-self="center" class="text-center">
|
<v-col cols="12" align-self="center" class="text-center">
|
||||||
({{ $tc("recipe.quantity") }}: {{ section.recipeScale }})
|
({{ $tc("recipe.quantity") }}: {{ recipeSection.recipeScale }})
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-for="(ingredientSection, ingredientSectionIndex) in recipeSection.ingredientSections"
|
||||||
|
:key="recipeSection.recipeId + recipeSectionIndex + ingredientSectionIndex"
|
||||||
|
>
|
||||||
|
<v-card-title v-if="ingredientSection.sectionName" class="ingredient-title mt-2 pb-0 text-h6">
|
||||||
|
{{ ingredientSection.sectionName }}
|
||||||
|
</v-card-title>
|
||||||
<div
|
<div
|
||||||
:class="$vuetify.breakpoint.smAndDown ? '' : 'ingredient-grid'"
|
:class="$vuetify.breakpoint.smAndDown ? '' : 'ingredient-grid'"
|
||||||
:style="$vuetify.breakpoint.smAndDown ? '' : { gridTemplateRows: `repeat(${Math.ceil(section.ingredients.length / 2)}, min-content)` }"
|
:style="$vuetify.breakpoint.smAndDown ? '' : { gridTemplateRows: `repeat(${Math.ceil(ingredientSection.ingredients.length / 2)}, min-content)` }"
|
||||||
>
|
>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-for="(ingredientData, i) in section.ingredients"
|
v-for="(ingredientData, i) in ingredientSection.ingredients"
|
||||||
:key="'ingredient' + i"
|
:key="recipeSection.recipeId + recipeSectionIndex + ingredientSectionIndex + i"
|
||||||
dense
|
dense
|
||||||
@click="recipeIngredientSections[sectionIndex].ingredients[i].checked = !recipeIngredientSections[sectionIndex].ingredients[i].checked"
|
@click="recipeIngredientSections[recipeSectionIndex]
|
||||||
|
.ingredientSections[ingredientSectionIndex]
|
||||||
|
.ingredients[i].checked = !recipeIngredientSections[recipeSectionIndex]
|
||||||
|
.ingredientSections[ingredientSectionIndex]
|
||||||
|
.ingredients[i]
|
||||||
|
.checked"
|
||||||
>
|
>
|
||||||
<v-checkbox
|
<v-checkbox
|
||||||
hide-details
|
hide-details
|
||||||
@@ -71,10 +96,12 @@
|
|||||||
<RecipeIngredientListItem
|
<RecipeIngredientListItem
|
||||||
:ingredient="ingredientData.ingredient"
|
:ingredient="ingredientData.ingredient"
|
||||||
:disable-amount="ingredientData.disableAmount"
|
:disable-amount="ingredientData.disableAmount"
|
||||||
:scale="section.recipeScale" />
|
:scale="recipeSection.recipeScale" />
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-end mb-4 mt-2">
|
<div class="d-flex justify-end mb-4 mt-2">
|
||||||
@@ -105,6 +132,7 @@ import { toRefs } from "@vueuse/core";
|
|||||||
import RecipeIngredientListItem from "./RecipeIngredientListItem.vue";
|
import RecipeIngredientListItem from "./RecipeIngredientListItem.vue";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
|
import { useShoppingListPreferences } from "~/composables/use-users/preferences";
|
||||||
import { ShoppingListSummary } from "~/lib/api/types/group";
|
import { ShoppingListSummary } from "~/lib/api/types/group";
|
||||||
import { Recipe, RecipeIngredient } from "~/lib/api/types/recipe";
|
import { Recipe, RecipeIngredient } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
@@ -112,17 +140,22 @@ export interface RecipeWithScale extends Recipe {
|
|||||||
scale: number;
|
scale: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShoppingListRecipeIngredient {
|
export interface ShoppingListIngredient {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
ingredient: RecipeIngredient;
|
ingredient: RecipeIngredient;
|
||||||
disableAmount: boolean;
|
disableAmount: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ShoppingListIngredientSection {
|
||||||
|
sectionName: string;
|
||||||
|
ingredients: ShoppingListIngredient[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface ShoppingListRecipeIngredientSection {
|
export interface ShoppingListRecipeIngredientSection {
|
||||||
recipeId: string;
|
recipeId: string;
|
||||||
recipeName: string;
|
recipeName: string;
|
||||||
recipeScale: number;
|
recipeScale: number;
|
||||||
ingredients: ShoppingListRecipeIngredient[];
|
ingredientSections: ShoppingListIngredientSection[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -144,8 +177,9 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
const { i18n } = useContext();
|
const { $auth, i18n } = useContext();
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
|
const preferences = useShoppingListPreferences();
|
||||||
|
|
||||||
// v-model support
|
// v-model support
|
||||||
const dialog = computed({
|
const dialog = computed({
|
||||||
@@ -163,6 +197,10 @@ export default defineComponent({
|
|||||||
shoppingListIngredientDialog: false,
|
shoppingListIngredientDialog: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const shoppingListChoices = computed(() => {
|
||||||
|
return props.shoppingLists.filter((list) => preferences.value.viewAllLists || list.userId === $auth.user?.id);
|
||||||
|
});
|
||||||
|
|
||||||
const recipeIngredientSections = ref<ShoppingListRecipeIngredientSection[]>([]);
|
const recipeIngredientSections = ref<ShoppingListRecipeIngredientSection[]>([]);
|
||||||
const selectedShoppingList = ref<ShoppingListSummary | null>(null);
|
const selectedShoppingList = ref<ShoppingListSummary | null>(null);
|
||||||
|
|
||||||
@@ -191,7 +229,7 @@ export default defineComponent({
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shoppingListIngredients: ShoppingListRecipeIngredient[] = recipe.recipeIngredient.map((ing) => {
|
const shoppingListIngredients: ShoppingListIngredient[] = recipe.recipeIngredient.map((ing) => {
|
||||||
return {
|
return {
|
||||||
checked: true,
|
checked: true,
|
||||||
ingredient: ing,
|
ingredient: ing,
|
||||||
@@ -199,11 +237,35 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const shoppingListIngredientSections = shoppingListIngredients.reduce((sections, ing) => {
|
||||||
|
// if title append new section to the end of the array
|
||||||
|
if (ing.ingredient.title) {
|
||||||
|
sections.push({
|
||||||
|
sectionName: ing.ingredient.title,
|
||||||
|
ingredients: [ing],
|
||||||
|
});
|
||||||
|
return sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
// append new section if first
|
||||||
|
if (sections.length === 0) {
|
||||||
|
sections.push({
|
||||||
|
sectionName: "",
|
||||||
|
ingredients: [ing],
|
||||||
|
});
|
||||||
|
return sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise add ingredient to last section in the array
|
||||||
|
sections[sections.length - 1].ingredients.push(ing);
|
||||||
|
return sections;
|
||||||
|
}, [] as ShoppingListIngredientSection[]);
|
||||||
|
|
||||||
recipeSectionMap.set(recipe.slug, {
|
recipeSectionMap.set(recipe.slug, {
|
||||||
recipeId: recipe.id,
|
recipeId: recipe.id,
|
||||||
recipeName: recipe.name,
|
recipeName: recipe.name,
|
||||||
recipeScale: recipe.scale,
|
recipeScale: recipe.scale,
|
||||||
ingredients: shoppingListIngredients,
|
ingredientSections: shoppingListIngredientSections,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,11 +293,13 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function bulkCheckIngredients(value = true) {
|
function bulkCheckIngredients(value = true) {
|
||||||
recipeIngredientSections.value.forEach((section) => {
|
recipeIngredientSections.value.forEach((recipeSection) => {
|
||||||
section.ingredients.forEach((ing) => {
|
recipeSection.ingredientSections.forEach((ingSection) => {
|
||||||
|
ingSection.ingredients.forEach((ing) => {
|
||||||
ing.checked = value;
|
ing.checked = value;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addRecipesToList() {
|
async function addRecipesToList() {
|
||||||
@@ -246,11 +310,13 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ingredients: RecipeIngredient[] = [];
|
const ingredients: RecipeIngredient[] = [];
|
||||||
section.ingredients.forEach((ing) => {
|
section.ingredientSections.forEach((ingSection) => {
|
||||||
|
ingSection.ingredients.forEach((ing) => {
|
||||||
if (ing.checked) {
|
if (ing.checked) {
|
||||||
ingredients.push(ing.ingredient);
|
ingredients.push(ing.ingredient);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (!ingredients.length) {
|
if (!ingredients.length) {
|
||||||
return;
|
return;
|
||||||
@@ -272,7 +338,11 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
success ? alert.success(i18n.t("recipe.recipes-added-to-list") as string)
|
const successMessage = promises.length === 1
|
||||||
|
? i18n.t("recipe.successfully-added-to-list") as string
|
||||||
|
: i18n.t("recipe.failed-to-add-to-list") as string;
|
||||||
|
|
||||||
|
success ? alert.success(successMessage)
|
||||||
: alert.error(i18n.t("failed-to-add-recipes-to-list") as string)
|
: alert.error(i18n.t("failed-to-add-recipes-to-list") as string)
|
||||||
|
|
||||||
state.shoppingListDialog = false;
|
state.shoppingListDialog = false;
|
||||||
@@ -282,6 +352,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
dialog,
|
dialog,
|
||||||
|
preferences,
|
||||||
|
shoppingListChoices,
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
addRecipesToList,
|
addRecipesToList,
|
||||||
bulkCheckIngredients,
|
bulkCheckIngredients,
|
||||||
|
|||||||
@@ -45,7 +45,6 @@
|
|||||||
:rating="recipe.rating"
|
:rating="recipe.rating"
|
||||||
:image="recipe.image"
|
:image="recipe.image"
|
||||||
:recipe-id="recipe.id"
|
:recipe-id="recipe.id"
|
||||||
:route="true"
|
|
||||||
v-on="$listeners.selected ? { selected: () => handleSelect(recipe) } : {}"
|
v-on="$listeners.selected ? { selected: () => handleSelect(recipe) } : {}"
|
||||||
/>
|
/>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|||||||
@@ -52,11 +52,20 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const ingredientCopyText = computed(() => {
|
const ingredientCopyText = computed(() => {
|
||||||
return props.value
|
const components: string[] = [];
|
||||||
.map((ingredient) => {
|
props.value.forEach((ingredient) => {
|
||||||
return `${parseIngredientText(ingredient, props.disableAmount, props.scale, false)}`;
|
if (ingredient.title) {
|
||||||
})
|
if (components.length) {
|
||||||
.join("\n");
|
components.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
components.push(`[${ingredient.title}]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
components.push(parseIngredientText(ingredient, props.disableAmount, props.scale, false));
|
||||||
|
});
|
||||||
|
|
||||||
|
return components.join("\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
function toggleChecked(index: number) {
|
function toggleChecked(index: number) {
|
||||||
|
|||||||
@@ -132,8 +132,7 @@ export default defineComponent({
|
|||||||
const { $auth, i18n } = useContext();
|
const { $auth, i18n } = useContext();
|
||||||
const domMadeThisForm = ref<VForm>();
|
const domMadeThisForm = ref<VForm>();
|
||||||
const newTimelineEvent = ref<RecipeTimelineEventIn>({
|
const newTimelineEvent = ref<RecipeTimelineEventIn>({
|
||||||
// @ts-expect-error - TS doesn't like the $auth global user attribute
|
subject: "",
|
||||||
subject: i18n.t("recipe.user-made-this", { user: $auth.user.fullName } as string),
|
|
||||||
eventType: "comment",
|
eventType: "comment",
|
||||||
eventMessage: "",
|
eventMessage: "",
|
||||||
timestamp: undefined,
|
timestamp: undefined,
|
||||||
@@ -178,6 +177,8 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
newTimelineEvent.value.recipeId = props.recipe.id
|
newTimelineEvent.value.recipeId = props.recipe.id
|
||||||
|
// @ts-expect-error - TS doesn't like the $auth global user attribute
|
||||||
|
newTimelineEvent.value.subject = i18n.t("recipe.user-made-this", { user: $auth.user.fullName })
|
||||||
|
|
||||||
// the user only selects the date, so we set the time to end of day local time
|
// the user only selects the date, so we set the time to end of day local time
|
||||||
// we choose the end of day so it always comes after "new recipe" events
|
// we choose the end of day so it always comes after "new recipe" events
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="edit" class="d-flex justify-end">
|
<div v-if="edit" class="d-flex justify-end">
|
||||||
<BaseButton class="ml-auto my-2" @click="addNote"> {{ $t("general.new") }}</BaseButton>
|
<BaseButton class="ml-auto my-2" @click="addNote"> {{ $t("general.add") }}</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -19,11 +19,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-list v-if="showViewer" dense class="mt-0 pt-0">
|
<v-list v-if="showViewer" dense class="mt-0 pt-0">
|
||||||
<v-list-item v-for="(item, key, index) in labels" :key="index" style="min-height: 25px" dense>
|
<v-list-item v-for="(item, key, index) in renderedList" :key="index" style="min-height: 25px" dense>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
<v-list-item-title class="pl-4 caption flex row">
|
<v-list-item-title class="pl-4 caption flex row">
|
||||||
<div>{{ item.label }}</div>
|
<div>{{ item.label }}</div>
|
||||||
<div class="ml-auto mr-1">{{ value[key] }}</div>
|
<div class="ml-auto mr-1">{{ item.value }}</div>
|
||||||
<div>{{ item.suffix }}</div>
|
<div>{{ item.suffix }}</div>
|
||||||
</v-list-item-title>
|
</v-list-item-title>
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
@@ -37,6 +37,14 @@
|
|||||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||||
import { Nutrition } from "~/lib/api/types/recipe";
|
import { Nutrition } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
|
type NutritionLabelType = {
|
||||||
|
[key: string]: {
|
||||||
|
label: string;
|
||||||
|
suffix: string;
|
||||||
|
value?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
@@ -50,34 +58,34 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
const { i18n } = useContext();
|
const { i18n } = useContext();
|
||||||
const labels = {
|
const labels = <NutritionLabelType>{
|
||||||
calories: {
|
calories: {
|
||||||
label: i18n.t("recipe.calories"),
|
label: i18n.tc("recipe.calories"),
|
||||||
suffix: i18n.t("recipe.calories-suffix"),
|
suffix: i18n.tc("recipe.calories-suffix"),
|
||||||
},
|
},
|
||||||
fatContent: {
|
fatContent: {
|
||||||
label: i18n.t("recipe.fat-content"),
|
label: i18n.tc("recipe.fat-content"),
|
||||||
suffix: i18n.t("recipe.grams"),
|
suffix: i18n.tc("recipe.grams"),
|
||||||
},
|
},
|
||||||
fiberContent: {
|
fiberContent: {
|
||||||
label: i18n.t("recipe.fiber-content"),
|
label: i18n.tc("recipe.fiber-content"),
|
||||||
suffix: i18n.t("recipe.grams"),
|
suffix: i18n.tc("recipe.grams"),
|
||||||
},
|
},
|
||||||
proteinContent: {
|
proteinContent: {
|
||||||
label: i18n.t("recipe.protein-content"),
|
label: i18n.tc("recipe.protein-content"),
|
||||||
suffix: i18n.t("recipe.grams"),
|
suffix: i18n.tc("recipe.grams"),
|
||||||
},
|
},
|
||||||
sodiumContent: {
|
sodiumContent: {
|
||||||
label: i18n.t("recipe.sodium-content"),
|
label: i18n.tc("recipe.sodium-content"),
|
||||||
suffix: i18n.t("recipe.milligrams"),
|
suffix: i18n.tc("recipe.milligrams"),
|
||||||
},
|
},
|
||||||
sugarContent: {
|
sugarContent: {
|
||||||
label: i18n.t("recipe.sugar-content"),
|
label: i18n.tc("recipe.sugar-content"),
|
||||||
suffix: i18n.t("recipe.grams"),
|
suffix: i18n.tc("recipe.grams"),
|
||||||
},
|
},
|
||||||
carbohydrateContent: {
|
carbohydrateContent: {
|
||||||
label: i18n.t("recipe.carbohydrate-content"),
|
label: i18n.tc("recipe.carbohydrate-content"),
|
||||||
suffix: i18n.t("recipe.grams"),
|
suffix: i18n.tc("recipe.grams"),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const valueNotNull = computed(() => {
|
const valueNotNull = computed(() => {
|
||||||
@@ -96,11 +104,25 @@ export default defineComponent({
|
|||||||
context.emit("input", { ...props.value, [key]: event });
|
context.emit("input", { ...props.value, [key]: event });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build a new list that only contains nutritional information that has a value
|
||||||
|
const renderedList = computed(() => {
|
||||||
|
return Object.entries(labels).reduce((item: NutritionLabelType, [key, label]) => {
|
||||||
|
if (props.value[key]?.trim()) {
|
||||||
|
item[key] = {
|
||||||
|
...label,
|
||||||
|
value: props.value[key],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}, {});
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
labels,
|
labels,
|
||||||
valueNotNull,
|
valueNotNull,
|
||||||
showViewer,
|
showViewer,
|
||||||
updateValue,
|
updateValue,
|
||||||
|
renderedList,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
<BaseDialog v-if="updateTarget" v-model="dialogs.update" :title="$t('general.update')" @confirm="updateOne()">
|
<BaseDialog v-if="updateTarget" v-model="dialogs.update" :title="$t('general.update')" @confirm="updateOne()">
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-text-field v-model="updateTarget.name" label="$t('general.name')"> </v-text-field>
|
<v-text-field v-model="updateTarget.name" :label="$t('general.name')"> </v-text-field>
|
||||||
<v-checkbox v-if="itemType === Organizer.Tool" v-model="updateTarget.onHand" :label="$t('tool.on-hand')"></v-checkbox>
|
<v-checkbox v-if="itemType === Organizer.Tool" v-model="updateTarget.onHand" :label="$t('tool.on-hand')"></v-checkbox>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
/>
|
/>
|
||||||
<div v-if="isEditForm" class="d-flex">
|
<div v-if="isEditForm" class="d-flex">
|
||||||
<RecipeDialogBulkAdd class="ml-auto my-2 mr-1" @bulk-data="addStep" />
|
<RecipeDialogBulkAdd class="ml-auto my-2 mr-1" @bulk-data="addStep" />
|
||||||
<BaseButton class="my-2" @click="addStep()"> {{ $t("general.new") }}</BaseButton>
|
<BaseButton class="my-2" @click="addStep()"> {{ $t("general.add") }}</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!$vuetify.breakpoint.mdAndUp">
|
<div v-if="!$vuetify.breakpoint.mdAndUp">
|
||||||
<RecipePageOrganizers :recipe="recipe" />
|
<RecipePageOrganizers :recipe="recipe" />
|
||||||
@@ -112,6 +112,7 @@ import { useUserApi } from "~/composables/api";
|
|||||||
import { uuid4, deepCopy } from "~/composables/use-utils";
|
import { uuid4, deepCopy } from "~/composables/use-utils";
|
||||||
import RecipeDialogBulkAdd from "~/components/Domain/Recipe/RecipeDialogBulkAdd.vue";
|
import RecipeDialogBulkAdd from "~/components/Domain/Recipe/RecipeDialogBulkAdd.vue";
|
||||||
import RecipeNotes from "~/components/Domain/Recipe/RecipeNotes.vue";
|
import RecipeNotes from "~/components/Domain/Recipe/RecipeNotes.vue";
|
||||||
|
import { useNavigationWarning } from "~/composables/use-navigation-warning";
|
||||||
|
|
||||||
const EDITOR_OPTIONS = {
|
const EDITOR_OPTIONS = {
|
||||||
mode: "code",
|
mode: "code",
|
||||||
@@ -151,6 +152,7 @@ export default defineComponent({
|
|||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const { pageMode, editMode, setMode, isEditForm, isEditJSON, isCookMode, isEditMode, toggleCookMode } =
|
const { pageMode, editMode, setMode, isEditForm, isEditJSON, isCookMode, isEditMode, toggleCookMode } =
|
||||||
usePageState(props.recipe.slug);
|
usePageState(props.recipe.slug);
|
||||||
|
const { deactivateNavigationWarning } = useNavigationWarning();
|
||||||
|
|
||||||
/** =============================================================
|
/** =============================================================
|
||||||
* Recipe Snapshot on Mount
|
* Recipe Snapshot on Mount
|
||||||
@@ -175,6 +177,7 @@ export default defineComponent({
|
|||||||
await api.recipes.updateOne(props.recipe.slug, props.recipe);
|
await api.recipes.updateOne(props.recipe.slug, props.recipe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
deactivateNavigationWarning();
|
||||||
});
|
});
|
||||||
|
|
||||||
/** =============================================================
|
/** =============================================================
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
<span>{{ parserToolTip }}</span>
|
<span>{{ parserToolTip }}</span>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
<RecipeDialogBulkAdd class="mx-1 mb-1" @bulk-data="addIngredient" />
|
<RecipeDialogBulkAdd class="mx-1 mb-1" @bulk-data="addIngredient" />
|
||||||
<BaseButton class="mb-1" @click="addIngredient" > {{ $t("general.new") }} </BaseButton>
|
<BaseButton class="mb-1" @click="addIngredient" > {{ $t("general.add") }} </BaseButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -148,10 +148,6 @@
|
|||||||
text: $tc('recipe.link-ingredients'),
|
text: $tc('recipe.link-ingredients'),
|
||||||
event: 'link-ingredients',
|
event: 'link-ingredients',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: $tc('recipe.merge-above'),
|
|
||||||
event: 'merge-above',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
text: $tc('recipe.upload-image'),
|
text: $tc('recipe.upload-image'),
|
||||||
event: 'upload-image'
|
event: 'upload-image'
|
||||||
@@ -160,11 +156,26 @@
|
|||||||
icon: previewStates[index] ? $globals.icons.edit : $globals.icons.eye,
|
icon: previewStates[index] ? $globals.icons.edit : $globals.icons.eye,
|
||||||
text: previewStates[index] ? $tc('recipe.edit-markdown') : $tc('markdown-editor.preview-markdown-button-label'),
|
text: previewStates[index] ? $tc('recipe.edit-markdown') : $tc('markdown-editor.preview-markdown-button-label'),
|
||||||
event: 'preview-step',
|
event: 'preview-step',
|
||||||
|
divider: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: $tc('recipe.merge-above'),
|
||||||
|
event: 'merge-above',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: $tc('recipe.move-to-top'),
|
||||||
|
event: 'move-to-top',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: $tc('recipe.move-to-bottom'),
|
||||||
|
event: 'move-to-bottom',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
@merge-above="mergeAbove(index - 1, index)"
|
@merge-above="mergeAbove(index - 1, index)"
|
||||||
|
@move-to-top="moveTo('top', index)"
|
||||||
|
@move-to-bottom="moveTo('bottom', index)"
|
||||||
@toggle-section="toggleShowTitle(step.id)"
|
@toggle-section="toggleShowTitle(step.id)"
|
||||||
@link-ingredients="openDialog(index, step.text, step.ingredientReferences)"
|
@link-ingredients="openDialog(index, step.text, step.ingredientReferences)"
|
||||||
@preview-step="togglePreviewState(index)"
|
@preview-step="togglePreviewState(index)"
|
||||||
@@ -531,6 +542,14 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function moveTo(dest: string, source: number) {
|
||||||
|
if (dest === "top") {
|
||||||
|
props.value.unshift(props.value.splice(source, 1)[0]);
|
||||||
|
} else {
|
||||||
|
props.value.push(props.value.splice(source, 1)[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const previewStates = ref<boolean[]>([]);
|
const previewStates = ref<boolean[]>([]);
|
||||||
|
|
||||||
function togglePreviewState(index: number) {
|
function togglePreviewState(index: number) {
|
||||||
@@ -646,6 +665,7 @@ export default defineComponent({
|
|||||||
getIngredientByRefId,
|
getIngredientByRefId,
|
||||||
showTitleEditor,
|
showTitleEditor,
|
||||||
mergeAbove,
|
mergeAbove,
|
||||||
|
moveTo,
|
||||||
openDialog,
|
openDialog,
|
||||||
setIngredientIds,
|
setIngredientIds,
|
||||||
availableNextStep,
|
availableNextStep,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
:label="$t('shopping-list.note')"
|
:label="$t('shopping-list.note')"
|
||||||
rows="1"
|
rows="1"
|
||||||
auto-grow
|
auto-grow
|
||||||
|
@keypress="handleNoteKeyPress"
|
||||||
></v-textarea>
|
></v-textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-end" style="gap: 20px">
|
<div class="d-flex align-end" style="gap: 20px">
|
||||||
@@ -95,7 +96,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, computed } from "@nuxtjs/composition-api";
|
import { defineComponent, computed, watch } from "@nuxtjs/composition-api";
|
||||||
import { ShoppingListItemCreate, ShoppingListItemOut } from "~/lib/api/types/group";
|
import { ShoppingListItemCreate, ShoppingListItemOut } from "~/lib/api/types/group";
|
||||||
import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
|
import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
|
||||||
import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
|
import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
|
||||||
@@ -128,9 +129,28 @@ export default defineComponent({
|
|||||||
context.emit("input", val);
|
context.emit("input", val);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value.food,
|
||||||
|
(newFood) => {
|
||||||
|
// @ts-ignore our logic already assumes there's a label attribute, even if TS doesn't think there is
|
||||||
|
listItem.value.label = newFood?.label || null;
|
||||||
|
listItem.value.labelId = listItem.value.label?.id || null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listItem,
|
listItem,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
handleNoteKeyPress(event) {
|
||||||
|
// Save on Enter
|
||||||
|
if (!event.shiftKey && event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
this.$emit("save");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,9 +10,12 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-list dense>
|
<v-list dense>
|
||||||
<v-list-item v-for="(child, idx) in btn.children" :key="idx" dense @click="$emit(child.event)">
|
<template v-for="(child, idx) in btn.children">
|
||||||
|
<v-list-item :key="idx" dense @click="$emit(child.event)">
|
||||||
<v-list-item-title>{{ child.text }}</v-list-item-title>
|
<v-list-item-title>{{ child.text }}</v-list-item-title>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<v-divider v-if="child.divider" :key="`divider-${idx}`" class="my-1"></v-divider>
|
||||||
|
</template>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
<v-tooltip
|
<v-tooltip
|
||||||
@@ -55,6 +58,7 @@ export interface ButtonOption {
|
|||||||
event: string;
|
event: string;
|
||||||
children?: ButtonOption[];
|
children?: ButtonOption[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
divider?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
$emit('submit');
|
$emit('submit');
|
||||||
dialog = false;
|
dialog = false;
|
||||||
"
|
"
|
||||||
|
@click:outside="$emit('cancel')"
|
||||||
|
@keydown.esc="$emit('cancel')"
|
||||||
>
|
>
|
||||||
<v-card height="100%">
|
<v-card height="100%">
|
||||||
<v-app-bar dark dense :color="color" class="">
|
<v-app-bar dark dense :color="color" class="">
|
||||||
|
|||||||
@@ -57,12 +57,12 @@
|
|||||||
:buttons="[
|
:buttons="[
|
||||||
{
|
{
|
||||||
icon: $globals.icons.edit,
|
icon: $globals.icons.edit,
|
||||||
text: $t('general.edit'),
|
text: $tc('general.edit'),
|
||||||
event: 'edit',
|
event: 'edit',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: $globals.icons.delete,
|
icon: $globals.icons.delete,
|
||||||
text: $t('general.delete'),
|
text: $tc('general.delete'),
|
||||||
event: 'delete',
|
event: 'delete',
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
@@ -160,6 +160,8 @@ export default defineComponent({
|
|||||||
props.bulkActions.forEach((action) => {
|
props.bulkActions.forEach((action) => {
|
||||||
handlers[action.event] = () => {
|
handlers[action.event] = () => {
|
||||||
context.emit(action.event, selected.value);
|
context.emit(action.event, selected.value);
|
||||||
|
// clear selection
|
||||||
|
selected.value = [];
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { computed, ComputedRef, ref, Ref, useContext } from "@nuxtjs/composition-api";
|
import { computed, ComputedRef, ref, Ref, useContext } from "@nuxtjs/composition-api";
|
||||||
import { UserOut } from "~/lib/api/types/user";
|
import { UserOut } from "~/lib/api/types/user";
|
||||||
|
import { useNavigationWarning } from "~/composables/use-navigation-warning";
|
||||||
|
|
||||||
export enum PageMode {
|
export enum PageMode {
|
||||||
EDIT = "EDIT",
|
EDIT = "EDIT",
|
||||||
@@ -65,6 +66,8 @@ function pageRefs(slug: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function pageState({ slugRef, pageModeRef, editModeRef, imageKey }: PageRefs): PageState {
|
function pageState({ slugRef, pageModeRef, editModeRef, imageKey }: PageRefs): PageState {
|
||||||
|
const { activateNavigationWarning, deactivateNavigationWarning } = useNavigationWarning();
|
||||||
|
|
||||||
const toggleEditMode = () => {
|
const toggleEditMode = () => {
|
||||||
if (editModeRef.value === EditorMode.FORM) {
|
if (editModeRef.value === EditorMode.FORM) {
|
||||||
editModeRef.value = EditorMode.JSON;
|
editModeRef.value = EditorMode.JSON;
|
||||||
@@ -88,9 +91,14 @@ function pageState({ slugRef, pageModeRef, editModeRef, imageKey }: PageRefs): P
|
|||||||
const setMode = (toMode: PageMode) => {
|
const setMode = (toMode: PageMode) => {
|
||||||
const fromMode = pageModeRef.value;
|
const fromMode = pageModeRef.value;
|
||||||
|
|
||||||
if (fromMode === PageMode.EDIT && toMode === PageMode.VIEW) {
|
if (fromMode === PageMode.EDIT) {
|
||||||
|
if (toMode === PageMode.VIEW) {
|
||||||
setEditMode(EditorMode.FORM);
|
setEditMode(EditorMode.FORM);
|
||||||
}
|
}
|
||||||
|
deactivateNavigationWarning();
|
||||||
|
} else if (toMode === PageMode.EDIT) {
|
||||||
|
activateNavigationWarning();
|
||||||
|
}
|
||||||
|
|
||||||
pageModeRef.value = toMode;
|
pageModeRef.value = toMode;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { usePublicExploreApi } from "../api/api-client";
|
|||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { IngredientFood } from "~/lib/api/types/recipe";
|
import { IngredientFood } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
let foodStore: Ref<IngredientFood[] | null> | null = null;
|
let foodStore: Ref<IngredientFood[] | null> = ref([]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* useFoodData returns a template reactive object
|
* useFoodData returns a template reactive object
|
||||||
@@ -39,11 +39,11 @@ export const usePublicFoodStore = function (groupSlug: string) {
|
|||||||
const actions = {
|
const actions = {
|
||||||
...usePublicStoreActions(api.foods, foodStore, loading),
|
...usePublicStoreActions(api.foods, foodStore, loading),
|
||||||
flushStore() {
|
flushStore() {
|
||||||
foodStore = null;
|
foodStore = ref([]);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!foodStore) {
|
if (!foodStore.value || foodStore.value.length === 0) {
|
||||||
foodStore = actions.getAll();
|
foodStore = actions.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,11 +57,11 @@ export const useFoodStore = function () {
|
|||||||
const actions = {
|
const actions = {
|
||||||
...useStoreActions(api.foods, foodStore, loading),
|
...useStoreActions(api.foods, foodStore, loading),
|
||||||
flushStore() {
|
flushStore() {
|
||||||
foodStore = null;
|
foodStore.value = [];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!foodStore) {
|
if (!foodStore.value || foodStore.value.length === 0) {
|
||||||
foodStore = actions.getAll();
|
foodStore = actions.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useStoreActions } from "../partials/use-actions-factory";
|
|||||||
import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
|
import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
|
||||||
let labelStore: Ref<MultiPurposeLabelOut[] | null> | null = null;
|
let labelStore: Ref<MultiPurposeLabelOut[] | null> = ref([]);
|
||||||
|
|
||||||
export function useLabelData() {
|
export function useLabelData() {
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
@@ -33,11 +33,11 @@ export function useLabelStore() {
|
|||||||
const actions = {
|
const actions = {
|
||||||
...useStoreActions<MultiPurposeLabelOut>(api.multiPurposeLabels, labelStore, loading),
|
...useStoreActions<MultiPurposeLabelOut>(api.multiPurposeLabels, labelStore, loading),
|
||||||
flushStore() {
|
flushStore() {
|
||||||
labelStore = null;
|
labelStore.value = [];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!labelStore) {
|
if (!labelStore.value || labelStore.value?.length === 0) {
|
||||||
labelStore = actions.getAll();
|
labelStore = actions.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useStoreActions } from "../partials/use-actions-factory";
|
|||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { IngredientUnit } from "~/lib/api/types/recipe";
|
import { IngredientUnit } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
let unitStore: Ref<IngredientUnit[] | null> | null = null;
|
let unitStore: Ref<IngredientUnit[] | null> = ref([]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* useUnitData returns a template reactive object
|
* useUnitData returns a template reactive object
|
||||||
@@ -40,11 +40,11 @@ export const useUnitStore = function () {
|
|||||||
const actions = {
|
const actions = {
|
||||||
...useStoreActions<IngredientUnit>(api.units, unitStore, loading),
|
...useStoreActions<IngredientUnit>(api.units, unitStore, loading),
|
||||||
flushStore() {
|
flushStore() {
|
||||||
unitStore = null;
|
unitStore.value = [];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!unitStore) {
|
if (!unitStore.value || unitStore.value.length === 0) {
|
||||||
unitStore = actions.getAll();
|
unitStore = actions.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ export const useCookbooks = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
return data;
|
||||||
},
|
},
|
||||||
async updateOne(updateData: UpdateCookBook) {
|
async updateOne(updateData: UpdateCookBook) {
|
||||||
if (!updateData.id) {
|
if (!updateData.id) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// This Code is auto generated by gen_global_components.py
|
// This Code is auto generated by gen_ts_locales.py
|
||||||
export const LOCALES = [
|
export const LOCALES = [
|
||||||
{
|
{
|
||||||
name: "繁體中文 (Chinese traditional)",
|
name: "繁體中文 (Chinese traditional)",
|
||||||
value: "zh-TW",
|
value: "zh-TW",
|
||||||
progress: 30,
|
progress: 29,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -15,7 +15,7 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "Tiếng Việt (Vietnamese)",
|
name: "Tiếng Việt (Vietnamese)",
|
||||||
value: "vi-VN",
|
value: "vi-VN",
|
||||||
progress: 1,
|
progress: 0,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -27,43 +27,43 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "Türkçe (Turkish)",
|
name: "Türkçe (Turkish)",
|
||||||
value: "tr-TR",
|
value: "tr-TR",
|
||||||
progress: 53,
|
progress: 62,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Svenska (Swedish)",
|
name: "Svenska (Swedish)",
|
||||||
value: "sv-SE",
|
value: "sv-SE",
|
||||||
progress: 94,
|
progress: 99,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "српски (Serbian)",
|
name: "српски (Serbian)",
|
||||||
value: "sr-SP",
|
value: "sr-SP",
|
||||||
progress: 32,
|
progress: 31,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Slovenian",
|
name: "Slovenian",
|
||||||
value: "sl-SI",
|
value: "sl-SI",
|
||||||
progress: 47,
|
progress: 49,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Slovak",
|
name: "Slovak",
|
||||||
value: "sk-SK",
|
value: "sk-SK",
|
||||||
progress: 93,
|
progress: 91,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Pусский (Russian)",
|
name: "Pусский (Russian)",
|
||||||
value: "ru-RU",
|
value: "ru-RU",
|
||||||
progress: 98,
|
progress: 99,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Română (Romanian)",
|
name: "Română (Romanian)",
|
||||||
value: "ro-RO",
|
value: "ro-RO",
|
||||||
progress: 42,
|
progress: 44,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -75,19 +75,19 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "Português do Brasil (Brazilian Portuguese)",
|
name: "Português do Brasil (Brazilian Portuguese)",
|
||||||
value: "pt-BR",
|
value: "pt-BR",
|
||||||
progress: 97,
|
progress: 95,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Polski (Polish)",
|
name: "Polski (Polish)",
|
||||||
value: "pl-PL",
|
value: "pl-PL",
|
||||||
progress: 98,
|
progress: 100,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Norsk (Norwegian)",
|
name: "Norsk (Norwegian)",
|
||||||
value: "no-NO",
|
value: "no-NO",
|
||||||
progress: 99,
|
progress: 97,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -99,25 +99,25 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "Latvian",
|
name: "Latvian",
|
||||||
value: "lv-LV",
|
value: "lv-LV",
|
||||||
progress: 1,
|
progress: 0,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Lithuanian",
|
name: "Lithuanian",
|
||||||
value: "lt-LT",
|
value: "lt-LT",
|
||||||
progress: 93,
|
progress: 91,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "한국어 (Korean)",
|
name: "한국어 (Korean)",
|
||||||
value: "ko-KR",
|
value: "ko-KR",
|
||||||
progress: 5,
|
progress: 3,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "日本語 (Japanese)",
|
name: "日本語 (Japanese)",
|
||||||
value: "ja-JP",
|
value: "ja-JP",
|
||||||
progress: 12,
|
progress: 11,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -135,25 +135,25 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "Magyar (Hungarian)",
|
name: "Magyar (Hungarian)",
|
||||||
value: "hu-HU",
|
value: "hu-HU",
|
||||||
progress: 100,
|
progress: 98,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Croatian",
|
name: "Croatian",
|
||||||
value: "hr-HR",
|
value: "hr-HR",
|
||||||
progress: 93,
|
progress: 91,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "עברית (Hebrew)",
|
name: "עברית (Hebrew)",
|
||||||
value: "he-IL",
|
value: "he-IL",
|
||||||
progress: 97,
|
progress: 98,
|
||||||
dir: "rtl",
|
dir: "rtl",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Galician",
|
name: "Galician",
|
||||||
value: "gl-ES",
|
value: "gl-ES",
|
||||||
progress: 1,
|
progress: 3,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -165,19 +165,19 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "French, Canada",
|
name: "French, Canada",
|
||||||
value: "fr-CA",
|
value: "fr-CA",
|
||||||
progress: 97,
|
progress: 95,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Suomi (Finnish)",
|
name: "Suomi (Finnish)",
|
||||||
value: "fi-FI",
|
value: "fi-FI",
|
||||||
progress: 91,
|
progress: 89,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Español (Spanish)",
|
name: "Español (Spanish)",
|
||||||
value: "es-ES",
|
value: "es-ES",
|
||||||
progress: 79,
|
progress: 93,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -189,13 +189,13 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "British English",
|
name: "British English",
|
||||||
value: "en-GB",
|
value: "en-GB",
|
||||||
progress: 3,
|
progress: 2,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Ελληνικά (Greek)",
|
name: "Ελληνικά (Greek)",
|
||||||
value: "el-GR",
|
value: "el-GR",
|
||||||
progress: 34,
|
progress: 33,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -219,7 +219,7 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "Català (Catalan)",
|
name: "Català (Catalan)",
|
||||||
value: "ca-ES",
|
value: "ca-ES",
|
||||||
progress: 75,
|
progress: 74,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -231,13 +231,13 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "العربية (Arabic)",
|
name: "العربية (Arabic)",
|
||||||
value: "ar-SA",
|
value: "ar-SA",
|
||||||
progress: 20,
|
progress: 18,
|
||||||
dir: "rtl",
|
dir: "rtl",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Afrikaans (Afrikaans)",
|
name: "Afrikaans (Afrikaans)",
|
||||||
value: "af-ZA",
|
value: "af-ZA",
|
||||||
progress: 92,
|
progress: 90,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
20
frontend/composables/use-navigation-warning.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export function useNavigationWarning() {
|
||||||
|
return { activateNavigationWarning, deactivateNavigationWarning };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a warning before the user navigates to another page
|
||||||
|
* e.g., by clicking a link (which isn't internal and rendered without page load),
|
||||||
|
* reloading the page,
|
||||||
|
* or closing the tab.
|
||||||
|
*/
|
||||||
|
const activateNavigationWarning = () => {
|
||||||
|
window.onbeforeunload = () => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables the warning when navigating to a page
|
||||||
|
*/
|
||||||
|
const deactivateNavigationWarning = () => {
|
||||||
|
window.onbeforeunload = null;
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ export interface UserRecipePreferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface UserShoppingListPreferences {
|
export interface UserShoppingListPreferences {
|
||||||
|
viewAllLists: boolean;
|
||||||
viewByLabel: boolean;
|
viewByLabel: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +71,7 @@ export function useShoppingListPreferences(): Ref<UserShoppingListPreferences> {
|
|||||||
const fromStorage = useLocalStorage(
|
const fromStorage = useLocalStorage(
|
||||||
"shopping-list-preferences",
|
"shopping-list-preferences",
|
||||||
{
|
{
|
||||||
|
viewAllLists: false,
|
||||||
viewByLabel: false,
|
viewByLabel: false,
|
||||||
},
|
},
|
||||||
{ mergeDefaults: true }
|
{ mergeDefaults: true }
|
||||||
|
|||||||
@@ -80,6 +80,7 @@
|
|||||||
"recipe-events": "Recipe Events"
|
"recipe-events": "Recipe Events"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"add": "Add",
|
||||||
"cancel": "Kanselleer",
|
"cancel": "Kanselleer",
|
||||||
"clear": "Maak skoon",
|
"clear": "Maak skoon",
|
||||||
"close": "Maak toe",
|
"close": "Maak toe",
|
||||||
@@ -142,6 +143,7 @@
|
|||||||
"save": "Stoor",
|
"save": "Stoor",
|
||||||
"settings": "Verstellings",
|
"settings": "Verstellings",
|
||||||
"share": "Deel",
|
"share": "Deel",
|
||||||
|
"show-all": "Show All",
|
||||||
"shuffle": "Skommel",
|
"shuffle": "Skommel",
|
||||||
"sort": "Sorteer",
|
"sort": "Sorteer",
|
||||||
"sort-alphabetically": "Alfabeties",
|
"sort-alphabetically": "Alfabeties",
|
||||||
@@ -199,7 +201,8 @@
|
|||||||
"upload-file": "Laai dokument op",
|
"upload-file": "Laai dokument op",
|
||||||
"created-on-date": "Geskep op: {0}",
|
"created-on-date": "Geskep op: {0}",
|
||||||
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes.",
|
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes.",
|
||||||
"clipboard-copy-failure": "Failed to copy to the clipboard."
|
"clipboard-copy-failure": "Failed to copy to the clipboard.",
|
||||||
|
"confirm-delete-generic-items": "Are you sure you want to delete the following items?"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"are-you-sure-you-want-to-delete-the-group": "Is jy seker jy wil <b>{groupName}<b/> uitvee?",
|
"are-you-sure-you-want-to-delete-the-group": "Is jy seker jy wil <b>{groupName}<b/> uitvee?",
|
||||||
@@ -258,6 +261,7 @@
|
|||||||
},
|
},
|
||||||
"meal-plan": {
|
"meal-plan": {
|
||||||
"create-a-new-meal-plan": "Skep 'n nuwe maaltydplan",
|
"create-a-new-meal-plan": "Skep 'n nuwe maaltydplan",
|
||||||
|
"update-this-meal-plan": "Update this Meal Plan",
|
||||||
"dinner-this-week": "Aandete hierdie week",
|
"dinner-this-week": "Aandete hierdie week",
|
||||||
"dinner-today": "Vandag se Aandete",
|
"dinner-today": "Vandag se Aandete",
|
||||||
"dinner-tonight": "VANAAND SE AANDETE",
|
"dinner-tonight": "VANAAND SE AANDETE",
|
||||||
@@ -472,9 +476,11 @@
|
|||||||
"add-to-timeline": "Voeg by tydlyn",
|
"add-to-timeline": "Voeg by tydlyn",
|
||||||
"recipe-added-to-list": "Resep by lys gevoeg",
|
"recipe-added-to-list": "Resep by lys gevoeg",
|
||||||
"recipes-added-to-list": "Recipes added to list",
|
"recipes-added-to-list": "Recipes added to list",
|
||||||
|
"successfully-added-to-list": "Successfully added to list",
|
||||||
"recipe-added-to-mealplan": "Resep is by die maaltydplan gevoeg",
|
"recipe-added-to-mealplan": "Resep is by die maaltydplan gevoeg",
|
||||||
"failed-to-add-recipes-to-list": "Failed to add recipe to list",
|
"failed-to-add-recipes-to-list": "Failed to add recipe to list",
|
||||||
"failed-to-add-recipe-to-mealplan": "Kon nie resep by maaltydplan voeg nie",
|
"failed-to-add-recipe-to-mealplan": "Kon nie resep by maaltydplan voeg nie",
|
||||||
|
"failed-to-add-to-list": "Failed to add to list",
|
||||||
"yield": "Resultaat",
|
"yield": "Resultaat",
|
||||||
"quantity": "Hoeveelheid",
|
"quantity": "Hoeveelheid",
|
||||||
"choose-unit": "Kies 'n eenheid",
|
"choose-unit": "Kies 'n eenheid",
|
||||||
@@ -490,6 +496,8 @@
|
|||||||
"cook-mode": "Kook modus",
|
"cook-mode": "Kook modus",
|
||||||
"link-ingredients": "Koppel bestanddele",
|
"link-ingredients": "Koppel bestanddele",
|
||||||
"merge-above": "Voeg bogenoemde saam",
|
"merge-above": "Voeg bogenoemde saam",
|
||||||
|
"move-to-bottom": "Move To Bottom",
|
||||||
|
"move-to-top": "Move To Top",
|
||||||
"reset-scale": "Stel skaal terug",
|
"reset-scale": "Stel skaal terug",
|
||||||
"decrease-scale-label": "Verminder die skaal met 1",
|
"decrease-scale-label": "Verminder die skaal met 1",
|
||||||
"increase-scale-label": "Verhoog skaal met 1",
|
"increase-scale-label": "Verhoog skaal met 1",
|
||||||
@@ -595,7 +603,7 @@
|
|||||||
"import-summary": "Invoeropsomming",
|
"import-summary": "Invoeropsomming",
|
||||||
"partial-backup": "Gedeeltelike back-up",
|
"partial-backup": "Gedeeltelike back-up",
|
||||||
"unable-to-delete-backup": "Kon nie back-up verwyder nie.",
|
"unable-to-delete-backup": "Kon nie back-up verwyder nie.",
|
||||||
"experimental-description": "Back-up skep 'n momentopname van die werf se databasis en data directory. Dit sluit alle data in en kan nie gestel word om substelle data uit te sluit nie. Jy kan dit as 'n momentopname van Mealie neem. Dit dien as 'n agnostiese manier om data uit te voer en in te voer, of om die webwerf na 'n eksterne ligging te back-up.",
|
"experimental-description": "Backups are total snapshots of the database and data directory of the site. This includes all data and cannot be set to exclude subsets of data. You can think of this as a snapshot of Mealie at a specific time. These serve as a database agnostic way to export and import data, or back up the site to an external location.",
|
||||||
"backup-restore": "Herlaai vanaf back-up",
|
"backup-restore": "Herlaai vanaf back-up",
|
||||||
"back-restore-description": "Die herstel van hierdie back-up sal alle huidige data in jou databasis en in die data-lêergids oorskryf. {cannot-be-undone} As die herstel suksesvol was, sal jy afgemeld word.",
|
"back-restore-description": "Die herstel van hierdie back-up sal alle huidige data in jou databasis en in die data-lêergids oorskryf. {cannot-be-undone} As die herstel suksesvol was, sal jy afgemeld word.",
|
||||||
"cannot-be-undone": "Hierdie aksie kan nie ongedaan gemaak word nie - gebruik met omsigtigheid.",
|
"cannot-be-undone": "Hierdie aksie kan nie ongedaan gemaak word nie - gebruik met omsigtigheid.",
|
||||||
@@ -1182,6 +1190,8 @@
|
|||||||
"require-all-tags": "Vereis alle merkers",
|
"require-all-tags": "Vereis alle merkers",
|
||||||
"require-all-tools": "Vereis alle kookgerei",
|
"require-all-tools": "Vereis alle kookgerei",
|
||||||
"cookbook-name": "Naam van die kookboek",
|
"cookbook-name": "Naam van die kookboek",
|
||||||
"cookbook-with-name": "Kookboek {0}"
|
"cookbook-with-name": "Kookboek {0}",
|
||||||
|
"create-a-cookbook": "Create a Cookbook",
|
||||||
|
"cookbook": "Cookbook"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@
|
|||||||
"recipe-events": "Recipe Events"
|
"recipe-events": "Recipe Events"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"add": "Add",
|
||||||
"cancel": "إلغاء",
|
"cancel": "إلغاء",
|
||||||
"clear": "مسح",
|
"clear": "مسح",
|
||||||
"close": "إغلاق",
|
"close": "إغلاق",
|
||||||
@@ -142,6 +143,7 @@
|
|||||||
"save": "حفظ",
|
"save": "حفظ",
|
||||||
"settings": "الإعدادات",
|
"settings": "الإعدادات",
|
||||||
"share": "مشاركة",
|
"share": "مشاركة",
|
||||||
|
"show-all": "Show All",
|
||||||
"shuffle": "ترتيب عشوائي",
|
"shuffle": "ترتيب عشوائي",
|
||||||
"sort": "ترتيب",
|
"sort": "ترتيب",
|
||||||
"sort-alphabetically": "ترتيب حَسَبَ الحروف الأبجدية",
|
"sort-alphabetically": "ترتيب حَسَبَ الحروف الأبجدية",
|
||||||
@@ -199,7 +201,8 @@
|
|||||||
"upload-file": "تحميل الملف",
|
"upload-file": "تحميل الملف",
|
||||||
"created-on-date": "تم الإنشاء في {0}",
|
"created-on-date": "تم الإنشاء في {0}",
|
||||||
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes.",
|
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes.",
|
||||||
"clipboard-copy-failure": "Failed to copy to the clipboard."
|
"clipboard-copy-failure": "Failed to copy to the clipboard.",
|
||||||
|
"confirm-delete-generic-items": "Are you sure you want to delete the following items?"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"are-you-sure-you-want-to-delete-the-group": "هل انت متأكد من رغبتك في حذف <b>{groupName}<b/>؟",
|
"are-you-sure-you-want-to-delete-the-group": "هل انت متأكد من رغبتك في حذف <b>{groupName}<b/>؟",
|
||||||
@@ -258,6 +261,7 @@
|
|||||||
},
|
},
|
||||||
"meal-plan": {
|
"meal-plan": {
|
||||||
"create-a-new-meal-plan": "إنشاء خطة وجبة جديدة",
|
"create-a-new-meal-plan": "إنشاء خطة وجبة جديدة",
|
||||||
|
"update-this-meal-plan": "Update this Meal Plan",
|
||||||
"dinner-this-week": "العشاء لهذا الأسبوع",
|
"dinner-this-week": "العشاء لهذا الأسبوع",
|
||||||
"dinner-today": "العشاء اليوم",
|
"dinner-today": "العشاء اليوم",
|
||||||
"dinner-tonight": "العشاء الليلة",
|
"dinner-tonight": "العشاء الليلة",
|
||||||
@@ -472,9 +476,11 @@
|
|||||||
"add-to-timeline": "Add to Timeline",
|
"add-to-timeline": "Add to Timeline",
|
||||||
"recipe-added-to-list": "Recipe added to list",
|
"recipe-added-to-list": "Recipe added to list",
|
||||||
"recipes-added-to-list": "Recipes added to list",
|
"recipes-added-to-list": "Recipes added to list",
|
||||||
|
"successfully-added-to-list": "Successfully added to list",
|
||||||
"recipe-added-to-mealplan": "Recipe added to mealplan",
|
"recipe-added-to-mealplan": "Recipe added to mealplan",
|
||||||
"failed-to-add-recipes-to-list": "Failed to add recipe to list",
|
"failed-to-add-recipes-to-list": "Failed to add recipe to list",
|
||||||
"failed-to-add-recipe-to-mealplan": "Failed to add recipe to mealplan",
|
"failed-to-add-recipe-to-mealplan": "Failed to add recipe to mealplan",
|
||||||
|
"failed-to-add-to-list": "Failed to add to list",
|
||||||
"yield": "Yield",
|
"yield": "Yield",
|
||||||
"quantity": "Quantity",
|
"quantity": "Quantity",
|
||||||
"choose-unit": "Choose Unit",
|
"choose-unit": "Choose Unit",
|
||||||
@@ -490,6 +496,8 @@
|
|||||||
"cook-mode": "Cook Mode",
|
"cook-mode": "Cook Mode",
|
||||||
"link-ingredients": "Link Ingredients",
|
"link-ingredients": "Link Ingredients",
|
||||||
"merge-above": "Merge Above",
|
"merge-above": "Merge Above",
|
||||||
|
"move-to-bottom": "Move To Bottom",
|
||||||
|
"move-to-top": "Move To Top",
|
||||||
"reset-scale": "Reset Scale",
|
"reset-scale": "Reset Scale",
|
||||||
"decrease-scale-label": "Decrease Scale by 1",
|
"decrease-scale-label": "Decrease Scale by 1",
|
||||||
"increase-scale-label": "Increase Scale by 1",
|
"increase-scale-label": "Increase Scale by 1",
|
||||||
@@ -595,7 +603,7 @@
|
|||||||
"import-summary": "Import Summary",
|
"import-summary": "Import Summary",
|
||||||
"partial-backup": "Partial Backup",
|
"partial-backup": "Partial Backup",
|
||||||
"unable-to-delete-backup": "Unable to Delete Backup.",
|
"unable-to-delete-backup": "Unable to Delete Backup.",
|
||||||
"experimental-description": "Backups a total snapshots of the database and data directory of the site. This includes all data and cannot be set to exclude subsets of data. You can think off this as a snapshot of Mealie at a specific time. Currently, {not-crossed-version} (data migrations are not done automatically). These serve as a database agnostic way to export and import data or backup the site to an external location.",
|
"experimental-description": "Backups are total snapshots of the database and data directory of the site. This includes all data and cannot be set to exclude subsets of data. You can think of this as a snapshot of Mealie at a specific time. These serve as a database agnostic way to export and import data, or back up the site to an external location.",
|
||||||
"backup-restore": "Backup Restore",
|
"backup-restore": "Backup Restore",
|
||||||
"back-restore-description": "Restoring this backup will overwrite all the current data in your database and in the data directory and replace them with the contents of this backup. {cannot-be-undone} If the restoration is successful, you will be logged out.",
|
"back-restore-description": "Restoring this backup will overwrite all the current data in your database and in the data directory and replace them with the contents of this backup. {cannot-be-undone} If the restoration is successful, you will be logged out.",
|
||||||
"cannot-be-undone": "This action cannot be undone - use with caution.",
|
"cannot-be-undone": "This action cannot be undone - use with caution.",
|
||||||
@@ -1182,6 +1190,8 @@
|
|||||||
"require-all-tags": "Require All Tags",
|
"require-all-tags": "Require All Tags",
|
||||||
"require-all-tools": "Require All Tools",
|
"require-all-tools": "Require All Tools",
|
||||||
"cookbook-name": "Cookbook Name",
|
"cookbook-name": "Cookbook Name",
|
||||||
"cookbook-with-name": "Cookbook {0}"
|
"cookbook-with-name": "Cookbook {0}",
|
||||||
|
"create-a-cookbook": "Create a Cookbook",
|
||||||
|
"cookbook": "Cookbook"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,9 +37,9 @@
|
|||||||
},
|
},
|
||||||
"category": {
|
"category": {
|
||||||
"categories": "Категории",
|
"categories": "Категории",
|
||||||
"category-created": "Категория създадена",
|
"category-created": "Категорията бе създадена",
|
||||||
"category-creation-failed": "Неуспешно създаване на категория",
|
"category-creation-failed": "Неуспешно създаване на категория",
|
||||||
"category-deleted": "Категория изтрита",
|
"category-deleted": "Категорията бе изтрита",
|
||||||
"category-deletion-failed": "Неуспешно изтриване на категория",
|
"category-deletion-failed": "Неуспешно изтриване на категория",
|
||||||
"category-filter": "Филтър за категории",
|
"category-filter": "Филтър за категории",
|
||||||
"category-update-failed": "Неуспешно актуализиране на категория",
|
"category-update-failed": "Неуспешно актуализиране на категория",
|
||||||
@@ -73,20 +73,21 @@
|
|||||||
"mealplan-events": "Известия за хранителен план",
|
"mealplan-events": "Известия за хранителен план",
|
||||||
"when-a-user-in-your-group-creates-a-new-mealplan": "Когато потребител от твоята потребителска група създаде нов хранителен план",
|
"when-a-user-in-your-group-creates-a-new-mealplan": "Когато потребител от твоята потребителска група създаде нов хранителен план",
|
||||||
"shopping-list-events": "Събития за списък за пазаруване",
|
"shopping-list-events": "Събития за списък за пазаруване",
|
||||||
"cookbook-events": "Събития за книга с рецепти",
|
"cookbook-events": "История на книгата с рецепти",
|
||||||
"tag-events": "Събития за таг",
|
"tag-events": "История на етикетите",
|
||||||
"category-events": "Събития за категория",
|
"category-events": "Събития за категория",
|
||||||
"when-a-new-user-joins-your-group": "Когато потребител се присъедини към твоята потребителска група",
|
"when-a-new-user-joins-your-group": "Когато потребител се присъедини към твоята потребителска група",
|
||||||
"recipe-events": "Събития на рецептата"
|
"recipe-events": "Събития на рецептата"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"add": "Add",
|
||||||
"cancel": "Откажи",
|
"cancel": "Откажи",
|
||||||
"clear": "Изчисти",
|
"clear": "Изчисти",
|
||||||
"close": "Затвори",
|
"close": "Затвори",
|
||||||
"confirm": "Потвърди",
|
"confirm": "Потвърди",
|
||||||
"confirm-delete-generic": "Сигурни ли сте, че желаете да изтриете това?",
|
"confirm-delete-generic": "Сигурни ли сте, че желаете да изтриете това?",
|
||||||
"copied_message": "Копирано!",
|
"copied_message": "Копирано!",
|
||||||
"create": "Създай",
|
"create": "Добави",
|
||||||
"created": "Създадено",
|
"created": "Създадено",
|
||||||
"custom": "Персонализиран",
|
"custom": "Персонализиран",
|
||||||
"dashboard": "Табло",
|
"dashboard": "Табло",
|
||||||
@@ -123,7 +124,7 @@
|
|||||||
"monday": "Понеделник",
|
"monday": "Понеделник",
|
||||||
"name": "Име",
|
"name": "Име",
|
||||||
"new": "Нов",
|
"new": "Нов",
|
||||||
"never": "Никога",
|
"never": "никога",
|
||||||
"no": "Не",
|
"no": "Не",
|
||||||
"no-recipe-found": "Няма намерени рецепти",
|
"no-recipe-found": "Няма намерени рецепти",
|
||||||
"ok": "Добре",
|
"ok": "Добре",
|
||||||
@@ -142,10 +143,11 @@
|
|||||||
"save": "Запази",
|
"save": "Запази",
|
||||||
"settings": "Настройки",
|
"settings": "Настройки",
|
||||||
"share": "Сподели",
|
"share": "Сподели",
|
||||||
|
"show-all": "Show All",
|
||||||
"shuffle": "Разбъркано",
|
"shuffle": "Разбъркано",
|
||||||
"sort": "Сортирай",
|
"sort": "Сортирай",
|
||||||
"sort-alphabetically": "По азбучен ред",
|
"sort-alphabetically": "По азбучен ред",
|
||||||
"status": "Статус",
|
"status": "състояние",
|
||||||
"subject": "Относно",
|
"subject": "Относно",
|
||||||
"submit": "Изпрати",
|
"submit": "Изпрати",
|
||||||
"success-count": "Успешни: {count}",
|
"success-count": "Успешни: {count}",
|
||||||
@@ -153,7 +155,7 @@
|
|||||||
"templates": "Шаблони:",
|
"templates": "Шаблони:",
|
||||||
"test": "Тест",
|
"test": "Тест",
|
||||||
"themes": "Теми",
|
"themes": "Теми",
|
||||||
"thursday": "Четвъртък",
|
"thursday": "четвъртък",
|
||||||
"token": "Токън",
|
"token": "Токън",
|
||||||
"tuesday": "Вторник",
|
"tuesday": "Вторник",
|
||||||
"type": "Тип",
|
"type": "Тип",
|
||||||
@@ -164,7 +166,7 @@
|
|||||||
"view": "Преглед",
|
"view": "Преглед",
|
||||||
"wednesday": "Сряда",
|
"wednesday": "Сряда",
|
||||||
"yes": "Да",
|
"yes": "Да",
|
||||||
"foods": "Храна",
|
"foods": "Продукти",
|
||||||
"units": "Мерни единици",
|
"units": "Мерни единици",
|
||||||
"back": "Назад",
|
"back": "Назад",
|
||||||
"next": "Напред",
|
"next": "Напред",
|
||||||
@@ -180,12 +182,12 @@
|
|||||||
"delete-with-name": "Изтриване {name}",
|
"delete-with-name": "Изтриване {name}",
|
||||||
"confirm-delete-generic-with-name": "Сигурни ли сте, че искате да изтриете това {name}?",
|
"confirm-delete-generic-with-name": "Сигурни ли сте, че искате да изтриете това {name}?",
|
||||||
"confirm-delete-own-admin-account": "Моля имайте предвид, че се опитвате да изтриете собствения си администраторски акаунт! Това действие не може да бъде отменени и ще изтриете окончателно Вашия акаунт?",
|
"confirm-delete-own-admin-account": "Моля имайте предвид, че се опитвате да изтриете собствения си администраторски акаунт! Това действие не може да бъде отменени и ще изтриете окончателно Вашия акаунт?",
|
||||||
"organizer": "Организиращ",
|
"organizer": "Органайзер",
|
||||||
"transfer": "Преместване",
|
"transfer": "Преместване",
|
||||||
"copy": "Копиране",
|
"copy": "Копиране",
|
||||||
"color": "Цвят",
|
"color": "Цвят",
|
||||||
"timestamp": "Времева отметка",
|
"timestamp": "Времева отметка",
|
||||||
"last-made": "Последно приготвено",
|
"last-made": "Последно приготвена на",
|
||||||
"learn-more": "Научи повече",
|
"learn-more": "Научи повече",
|
||||||
"this-feature-is-currently-inactive": "Тази функционалност в момента е неактивна",
|
"this-feature-is-currently-inactive": "Тази функционалност в момента е неактивна",
|
||||||
"clipboard-not-supported": "Не се поддържа клипборд",
|
"clipboard-not-supported": "Не се поддържа клипборд",
|
||||||
@@ -195,11 +197,12 @@
|
|||||||
"actions": "Действия",
|
"actions": "Действия",
|
||||||
"selected-count": "Избрано: {count}",
|
"selected-count": "Избрано: {count}",
|
||||||
"export-all": "Експортиране на всички",
|
"export-all": "Експортиране на всички",
|
||||||
"refresh": "Опресни",
|
"refresh": "Опресняване",
|
||||||
"upload-file": "Качване на файл",
|
"upload-file": "Качване на файл",
|
||||||
"created-on-date": "Създадено на {0}",
|
"created-on-date": "Създадено на {0}",
|
||||||
"unsaved-changes": "Имате незапазени промени. Желаете ли да ги запазите преди да излезете? Натиснете Ок за запазване и Отказ за отхвърляне на промените.",
|
"unsaved-changes": "Имате незапазени промени. Желаете ли да ги запазите преди да излезете? Натиснете Ок за запазване и Отказ за отхвърляне на промените.",
|
||||||
"clipboard-copy-failure": "Линкът към рецептата е копиран в клипборда."
|
"clipboard-copy-failure": "Линкът към рецептата е копиран в клипборда.",
|
||||||
|
"confirm-delete-generic-items": "Сигурни ли сте, че желаете да изтриете следните елементи?"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"are-you-sure-you-want-to-delete-the-group": "Сигурни ли сте, че искате да изтриете <b>{groupName}<b/>?",
|
"are-you-sure-you-want-to-delete-the-group": "Сигурни ли сте, че искате да изтриете <b>{groupName}<b/>?",
|
||||||
@@ -226,7 +229,7 @@
|
|||||||
"keep-my-recipes-private-description": "Задай групата и всичките рецепти като лични. Винаги може да промените това по-късно."
|
"keep-my-recipes-private-description": "Задай групата и всичките рецепти като лични. Винаги може да промените това по-късно."
|
||||||
},
|
},
|
||||||
"manage-members": "Управление на потребителите",
|
"manage-members": "Управление на потребителите",
|
||||||
"manage-members-description": "Управлявай правата на потребителите в твоите групи. {manage} позволява на потребителя да достъпи страницата за управление на данни {invite} позволява на потребителя да генерира линк за присъединяване за други потребители. Собствениците на група не могат да променят своите права.",
|
"manage-members-description": "Настройки на правата на потребителите в твоите групи. {manage} позволява на потребителя да достъпи страницата за управление на данни {invite} позволява на потребителя да генерира линк за присъединяване за други потребители. Собствениците на група не могат да променят своите права.",
|
||||||
"manage": "Управление",
|
"manage": "Управление",
|
||||||
"invite": "Покани",
|
"invite": "Покани",
|
||||||
"looking-to-update-your-profile": "Търсите да обновите собствения си профил?",
|
"looking-to-update-your-profile": "Търсите да обновите собствения си профил?",
|
||||||
@@ -237,7 +240,7 @@
|
|||||||
"private-group-description": "Задаването на групата като лична ще зададе всички настройки за публично виждане към стандартните. Това е с по-висок приоритет от индивидуалните настройки за публично виждане на всяка една рецепта.",
|
"private-group-description": "Задаването на групата като лична ще зададе всички настройки за публично виждане към стандартните. Това е с по-висок приоритет от индивидуалните настройки за публично виждане на всяка една рецепта.",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes": "Разрешете на потребители извън вашата група да виждат рецептите Ви",
|
"allow-users-outside-of-your-group-to-see-your-recipes": "Разрешете на потребители извън вашата група да виждат рецептите Ви",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Когато е пуснато ще може да генерирате публичен линк за споделяне на рецепти без да е нужно потребителя да се нуждае от вписване. Когато е изключено, ще можете да споделяте рецепти само с потребители, които са във Вашата група или чрез предварително генериран личен линк за споделяне.",
|
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Когато е пуснато ще може да генерирате публичен линк за споделяне на рецепти без да е нужно потребителя да се нуждае от вписване. Когато е изключено, ще можете да споделяте рецепти само с потребители, които са във Вашата група или чрез предварително генериран личен линк за споделяне.",
|
||||||
"show-nutrition-information": "Показвай информация за храната",
|
"show-nutrition-information": "Показвай информация за хранителните стойности",
|
||||||
"show-nutrition-information-description": "Когато е пуснато, информацията за хранителната стойност на рецептата ще бъде показана, ако е налична. Ако няма информация за хранителната стойност, тогава тя няма да бъде показана.",
|
"show-nutrition-information-description": "Когато е пуснато, информацията за хранителната стойност на рецептата ще бъде показана, ако е налична. Ако няма информация за хранителната стойност, тогава тя няма да бъде показана.",
|
||||||
"show-recipe-assets": "Покажи медия файловете на рецептата",
|
"show-recipe-assets": "Покажи медия файловете на рецептата",
|
||||||
"show-recipe-assets-description": "Когато е пуснато, медия файловете ще бъдат показани към рецептата, ако са налични.",
|
"show-recipe-assets-description": "Когато е пуснато, медия файловете ще бъдат показани към рецептата, ако са налични.",
|
||||||
@@ -245,8 +248,8 @@
|
|||||||
"default-to-landscape-view-description": "Когато е пуснато, раздела за главната информация на рецептата ще бъде показан в пейзажен режим",
|
"default-to-landscape-view-description": "Когато е пуснато, раздела за главната информация на рецептата ще бъде показан в пейзажен режим",
|
||||||
"disable-users-from-commenting-on-recipes": "Забрани коментирането на рецепти от потребителите",
|
"disable-users-from-commenting-on-recipes": "Забрани коментирането на рецепти от потребителите",
|
||||||
"disable-users-from-commenting-on-recipes-description": "Скрива раздела за коментари към рецептата и забранява коментирането",
|
"disable-users-from-commenting-on-recipes-description": "Скрива раздела за коментари към рецептата и забранява коментирането",
|
||||||
"disable-organizing-recipe-ingredients-by-units-and-food": "Изключи организирането на съставките на рецепта по мерни единици и храна",
|
"disable-organizing-recipe-ingredients-by-units-and-food": "Изключи организирането на съставките по мерни единици и продукти",
|
||||||
"disable-organizing-recipe-ingredients-by-units-and-food-description": "Скрива полетата за храната, мерната единица и количеството за съставките и третира съставките като полета със свободен текст.",
|
"disable-organizing-recipe-ingredients-by-units-and-food-description": "Скрива полетата за продукт, мерна единица и количество и третира съставките като полета със свободен текст.",
|
||||||
"general-preferences": "Общи предпочитания",
|
"general-preferences": "Общи предпочитания",
|
||||||
"group-recipe-preferences": "Предпочитания за рецепта по група",
|
"group-recipe-preferences": "Предпочитания за рецепта по група",
|
||||||
"report": "Сигнал",
|
"report": "Сигнал",
|
||||||
@@ -258,40 +261,41 @@
|
|||||||
},
|
},
|
||||||
"meal-plan": {
|
"meal-plan": {
|
||||||
"create-a-new-meal-plan": "Създаване на нов хранителен план",
|
"create-a-new-meal-plan": "Създаване на нов хранителен план",
|
||||||
|
"update-this-meal-plan": "Обнови този План за хранене",
|
||||||
"dinner-this-week": "Вечеря тази седмица",
|
"dinner-this-week": "Вечеря тази седмица",
|
||||||
"dinner-today": "Вечеря Днес",
|
"dinner-today": "Вечеря Днес",
|
||||||
"dinner-tonight": "Вечеря ТАЗИ ВЕЧЕР",
|
"dinner-tonight": "Вечеря ТАЗИ ВЕЧЕР",
|
||||||
"edit-meal-plan": "Редактиране на хранителен план",
|
"edit-meal-plan": "Редактиране на планираното меню",
|
||||||
"end-date": "Крайна дата",
|
"end-date": "Крайна дата",
|
||||||
"group": "Група (Бета версия)",
|
"group": "Група (Бета версия)",
|
||||||
"main": "Основен",
|
"main": "Основен",
|
||||||
"meal-planner": "Планиране на хранене",
|
"meal-planner": "Планиране на менюта",
|
||||||
"meal-plans": "Хранителни планове",
|
"meal-plans": "Планирани менюта",
|
||||||
"mealplan-categories": "Категории на хранителния план",
|
"mealplan-categories": "Категории на менюто",
|
||||||
"mealplan-created": "Планът за хранене е създаден",
|
"mealplan-created": "Менюто бе създадено",
|
||||||
"mealplan-creation-failed": "Неуспешно създаване на план за хранене",
|
"mealplan-creation-failed": "Неуспешно създаване на меню",
|
||||||
"mealplan-deleted": "Планът за хранене е изтрит",
|
"mealplan-deleted": "Менюто бе изтрито",
|
||||||
"mealplan-deletion-failed": "Неуспешно изтриване на план за хранене",
|
"mealplan-deletion-failed": "Неуспешно изтриване на меню",
|
||||||
"mealplan-settings": "Настройки на плана за хранене",
|
"mealplan-settings": "Настройки на менюто",
|
||||||
"mealplan-update-failed": "Неуспешно обновяване на план за хранене",
|
"mealplan-update-failed": "Неуспешно обновяване на седмичното меню",
|
||||||
"mealplan-updated": "Планът за хранене е обновен",
|
"mealplan-updated": "Седмичното меню бе обновено",
|
||||||
"no-meal-plan-defined-yet": "Все още няма дефинирани планове за хранене",
|
"no-meal-plan-defined-yet": "Все още няма създадено седмично меню",
|
||||||
"no-meal-planned-for-today": "Няма хранителен план за днес",
|
"no-meal-planned-for-today": "За днес няма планирано меню",
|
||||||
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Само рецептите от тези категории ще бъдат използвани в хранителните планове",
|
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Само рецептите от тези категории ще бъдат използвани в хранителните планове",
|
||||||
"planner": "Планьор",
|
"planner": "Планьор",
|
||||||
"quick-week": "Бърза седмица",
|
"quick-week": "Бърза седмица",
|
||||||
"side": "Страна",
|
"side": "Предястие",
|
||||||
"sides": "Страни",
|
"sides": "Страни",
|
||||||
"start-date": "Начална дата",
|
"start-date": "Начална дата",
|
||||||
"rule-day": "Правило за деня",
|
"rule-day": "Ден от седмицата",
|
||||||
"meal-type": "Тип на ястието",
|
"meal-type": "Вид ястие",
|
||||||
"breakfast": "Закуска",
|
"breakfast": "Закуска",
|
||||||
"lunch": "Обяд",
|
"lunch": "обяд",
|
||||||
"dinner": "Вечеря",
|
"dinner": "Вечеря",
|
||||||
"type-any": "Който и да е",
|
"type-any": "Всички",
|
||||||
"day-any": "Който и да е",
|
"day-any": "Всички",
|
||||||
"editor": "Редактор",
|
"editor": "Редактор",
|
||||||
"meal-recipe": "Рецепта за хранене",
|
"meal-recipe": "Рецепта за ястие",
|
||||||
"meal-title": "Заглавие на рецептата",
|
"meal-title": "Заглавие на рецептата",
|
||||||
"meal-note": "Бележка към рецептата",
|
"meal-note": "Бележка към рецептата",
|
||||||
"note-only": "Само бележка",
|
"note-only": "Само бележка",
|
||||||
@@ -301,15 +305,15 @@
|
|||||||
"this-rule-will-apply": "Това правило ще се приложи на {dayCriteria} {mealTypeCriteria}.",
|
"this-rule-will-apply": "Това правило ще се приложи на {dayCriteria} {mealTypeCriteria}.",
|
||||||
"to-all-days": "за всички дни",
|
"to-all-days": "за всички дни",
|
||||||
"on-days": "на {0}",
|
"on-days": "на {0}",
|
||||||
"for-all-meal-types": "за всички типове ястия",
|
"for-all-meal-types": "за всички видове ястия",
|
||||||
"for-type-meal-types": "за {0} типове ястия",
|
"for-type-meal-types": "за {0}",
|
||||||
"meal-plan-rules": "Правила на плана за хранене",
|
"meal-plan-rules": "Правила за съставяне на седмично меню",
|
||||||
"new-rule": "Ново правило",
|
"new-rule": "Ново правило",
|
||||||
"meal-plan-rules-description": "Може да създадете правила за автоматично избиране на рецепти от хранителните планове. Тези правила ще бъдат използвани за попълване на списъка от произволното избрани рецепти, от които да избирате, когато създавате нов хранителен план. Бележка: ако тези правила имат еднакви ограничения по ден/тип, тогава техните категории ще бъдат обединени. На практика, е ненужно да създавате дублирани правила, но все пак това е възможно.",
|
"meal-plan-rules-description": "Може да създадете правила за автоматично избиране на рецепти в седмичното меню. Тези правила ще бъдат използвани за създаване на седмично маню от произволното избрани рецепти. Бележка: ако тези правила имат еднакви ограничения по ден/тип, тогава техните категории ще бъдат обединени. На практика, не е нужно да създавате дублиращи се правила, но все пак това е възможно.",
|
||||||
"new-rule-description": "Когато създавате ново правило за хранителен план, Вие ще може да зададете ограничение за правилото да бъде приложено за определен ден от седмицата и/или специфичен тип ястие. За да добавите правило за всички дни или всички типове ястия, Вие може да зададете правилото като \"Всички\", което ще го приложи за всички дни и/или типове ястия.",
|
"new-rule-description": "Когато създавате ново правило за създаване на седмично меню, може да зададете ограничение правилото да бъде приложено за определен ден от седмицата и/или специфичен вид ястие. За да добавите правило за всички дни или всички типове ястия, Вие може да изберете \"Всички\", което ще го приложи за всички дни и/или видове ястия.",
|
||||||
"recipe-rules": "Правила на рецептата",
|
"recipe-rules": "Правила на рецептата",
|
||||||
"applies-to-all-days": "Прилага се за всички дни",
|
"applies-to-all-days": "Прилага се за всички дни",
|
||||||
"applies-on-days": "Прилага се на {0}",
|
"applies-on-days": "Всеки/всяка {0}",
|
||||||
"meal-plan-settings": "Настройки на плана за хранене"
|
"meal-plan-settings": "Настройки на плана за хранене"
|
||||||
},
|
},
|
||||||
"migration": {
|
"migration": {
|
||||||
@@ -348,7 +352,7 @@
|
|||||||
"recipe-data-migrations": "Миграция на данни на рецепти",
|
"recipe-data-migrations": "Миграция на данни на рецепти",
|
||||||
"recipe-data-migrations-explanation": "Рецептите могат да бъдат мигрирани от други приложения поддържани от Mealie. Това е добър начин да започнете използването си на Mealie.",
|
"recipe-data-migrations-explanation": "Рецептите могат да бъдат мигрирани от други приложения поддържани от Mealie. Това е добър начин да започнете използването си на Mealie.",
|
||||||
"choose-migration-type": "Избери тип на миграцията",
|
"choose-migration-type": "Избери тип на миграцията",
|
||||||
"tag-all-recipes": "Отбележи всички рецепти с {tag-name} таг",
|
"tag-all-recipes": "Добави {tag-name} като етикет във всички рецепти",
|
||||||
"nextcloud-text": "Nextcloud рецептите могат да бъдат импортирани от .zip файл, който съдържа данни съхранени в Nextcloud. Вижте примерната структура на папките по-долу за да се подсигурите, че рецептите Ви могат да бъдат импортирани.",
|
"nextcloud-text": "Nextcloud рецептите могат да бъдат импортирани от .zip файл, който съдържа данни съхранени в Nextcloud. Вижте примерната структура на папките по-долу за да се подсигурите, че рецептите Ви могат да бъдат импортирани.",
|
||||||
"chowdown-text": "Mealie поддържа формата на хранилището на Chowdown. Свалете кода на хранилището като .zip файл и го качете по-долу",
|
"chowdown-text": "Mealie поддържа формата на хранилището на Chowdown. Свалете кода на хранилището като .zip файл и го качете по-долу",
|
||||||
"recipe-1": "Рецепта 1",
|
"recipe-1": "Рецепта 1",
|
||||||
@@ -356,7 +360,7 @@
|
|||||||
"paprika-text": "Mealie може да импортирай рецепти от приложението Paprika. Експортирайте рецептите си от Paprika, преименувате файловото разширение на .zip и го качете по-долу.",
|
"paprika-text": "Mealie може да импортирай рецепти от приложението Paprika. Експортирайте рецептите си от Paprika, преименувате файловото разширение на .zip и го качете по-долу.",
|
||||||
"mealie-text": "Mealie може да импортира рецепти от Mealie преди версия 1.0. Експортирайте рецептите от старата си инстанция и ги качете като .zip файл по-долу. Бележка: могат да бъдат импортирани само рецептите.",
|
"mealie-text": "Mealie може да импортира рецепти от Mealie преди версия 1.0. Експортирайте рецептите от старата си инстанция и ги качете като .zip файл по-долу. Бележка: могат да бъдат импортирани само рецептите.",
|
||||||
"plantoeat": {
|
"plantoeat": {
|
||||||
"title": "Plan to Eat",
|
"title": "Планиране на меню",
|
||||||
"description-long": "Mealie може да импортира рецепти от Plan to Eat."
|
"description-long": "Mealie може да импортира рецепти от Plan to Eat."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -451,7 +455,7 @@
|
|||||||
"remove-section": "Премахни раздел",
|
"remove-section": "Премахни раздел",
|
||||||
"save-recipe-before-use": "Запази рецептата преди да я използваш",
|
"save-recipe-before-use": "Запази рецептата преди да я използваш",
|
||||||
"section-title": "Заглавие на раздела",
|
"section-title": "Заглавие на раздела",
|
||||||
"servings": "Порции",
|
"servings": "Порция|порции",
|
||||||
"share-recipe-message": "Искам да споделя моята рецепта {0} с теб.",
|
"share-recipe-message": "Искам да споделя моята рецепта {0} с теб.",
|
||||||
"show-nutrition-values": "Покажи хранителните стойности",
|
"show-nutrition-values": "Покажи хранителните стойности",
|
||||||
"sodium-content": "Натрий",
|
"sodium-content": "Натрий",
|
||||||
@@ -469,17 +473,19 @@
|
|||||||
"date-format-hint-yyyy-mm-dd": "YYYY-MM-DD формат",
|
"date-format-hint-yyyy-mm-dd": "YYYY-MM-DD формат",
|
||||||
"add-to-list": "Добави към списък",
|
"add-to-list": "Добави към списък",
|
||||||
"add-to-plan": "Добави към план",
|
"add-to-plan": "Добави към план",
|
||||||
"add-to-timeline": "Добави към времевата линия",
|
"add-to-timeline": "Добави към историята на събитията",
|
||||||
"recipe-added-to-list": "Рецептата е добавена към списъка",
|
"recipe-added-to-list": "Рецептата е добавена към списъка",
|
||||||
"recipes-added-to-list": "Рецептите са добавени към списъка",
|
"recipes-added-to-list": "Рецептите са добавени към списъка",
|
||||||
|
"successfully-added-to-list": "Успешно добавено в списъка",
|
||||||
"recipe-added-to-mealplan": "Рецептата е добавена към хранителния план",
|
"recipe-added-to-mealplan": "Рецептата е добавена към хранителния план",
|
||||||
"failed-to-add-recipes-to-list": "Неуспешно добавяне на рецепта към списъка",
|
"failed-to-add-recipes-to-list": "Неуспешно добавяне на рецепта към списъка",
|
||||||
"failed-to-add-recipe-to-mealplan": "Рецептата не беше добавена към хранителния план",
|
"failed-to-add-recipe-to-mealplan": "Рецептата не беше добавена към хранителния план",
|
||||||
|
"failed-to-add-to-list": "Неуспешно добавяне към списъка",
|
||||||
"yield": "Добив",
|
"yield": "Добив",
|
||||||
"quantity": "Количество",
|
"quantity": "Количество",
|
||||||
"choose-unit": "Избери единица",
|
"choose-unit": "Избери единица",
|
||||||
"press-enter-to-create": "Натисните Enter за да създадете",
|
"press-enter-to-create": "Натисните Enter за да създадете",
|
||||||
"choose-food": "Избери храна",
|
"choose-food": "Избери продукт",
|
||||||
"notes": "Бележки",
|
"notes": "Бележки",
|
||||||
"toggle-section": "Превключване на раздела",
|
"toggle-section": "Превключване на раздела",
|
||||||
"see-original-text": "Виж оригиналния текст",
|
"see-original-text": "Виж оригиналния текст",
|
||||||
@@ -490,6 +496,8 @@
|
|||||||
"cook-mode": "Режим на готвене",
|
"cook-mode": "Режим на готвене",
|
||||||
"link-ingredients": "Свържи съставките",
|
"link-ingredients": "Свържи съставките",
|
||||||
"merge-above": "Обедини с по-горната",
|
"merge-above": "Обедини с по-горната",
|
||||||
|
"move-to-bottom": "Премести най-долу",
|
||||||
|
"move-to-top": "Премести най-горе",
|
||||||
"reset-scale": "Оригинален мащаб",
|
"reset-scale": "Оригинален мащаб",
|
||||||
"decrease-scale-label": "Намали мащаба с 1",
|
"decrease-scale-label": "Намали мащаба с 1",
|
||||||
"increase-scale-label": "Увеличи мащаба с 1",
|
"increase-scale-label": "Увеличи мащаба с 1",
|
||||||
@@ -502,12 +510,12 @@
|
|||||||
"resume-timer": "Възобновяване на таймера",
|
"resume-timer": "Възобновяване на таймера",
|
||||||
"stop-timer": "Спри таймера"
|
"stop-timer": "Спри таймера"
|
||||||
},
|
},
|
||||||
"edit-timeline-event": "Редактирай събитие от времевата линия",
|
"edit-timeline-event": "Редактирай събитие",
|
||||||
"timeline": "Времева линия",
|
"timeline": "Хронология на събитията",
|
||||||
"timeline-is-empty": "Няма нищо във времевата линия. Опитайте да приготвите рецептата!",
|
"timeline-is-empty": "Няма история на събитията. Опитайте да приготвите рецептата!",
|
||||||
"group-global-timeline": "{groupName} глобална времева линия",
|
"group-global-timeline": "{groupName} История на събитията",
|
||||||
"open-timeline": "Отвори времевата линия",
|
"open-timeline": "Отвори историята на събитията",
|
||||||
"made-this": "Аз направих това",
|
"made-this": "Сготвих рецептата",
|
||||||
"how-did-it-turn-out": "Как се получи?",
|
"how-did-it-turn-out": "Как се получи?",
|
||||||
"user-made-this": "{user} направи това",
|
"user-made-this": "{user} направи това",
|
||||||
"last-made-date": "Последно приготвена на {date}",
|
"last-made-date": "Последно приготвена на {date}",
|
||||||
@@ -522,29 +530,29 @@
|
|||||||
"edit-markdown": "Редактирай с markdown",
|
"edit-markdown": "Редактирай с markdown",
|
||||||
"recipe-creation": "Създаване на рецепта",
|
"recipe-creation": "Създаване на рецепта",
|
||||||
"select-one-of-the-various-ways-to-create-a-recipe": "Изберете един от разнообразните начини за създаване на рецепта",
|
"select-one-of-the-various-ways-to-create-a-recipe": "Изберете един от разнообразните начини за създаване на рецепта",
|
||||||
"looking-for-migrations": "Търсите миграциите?",
|
"looking-for-migrations": "Миграция на данни",
|
||||||
"import-with-url": "Импортирай от линк",
|
"import-with-url": "Импортирай от линк",
|
||||||
"create-recipe": "Създай рецепта",
|
"create-recipe": "Добави рецепта",
|
||||||
"import-with-zip": "Импортирай от .zip",
|
"import-with-zip": "Импортирай от .zip",
|
||||||
"create-recipe-from-an-image": "Създай рецепта от снимка",
|
"create-recipe-from-an-image": "Добави рецепта от снимка",
|
||||||
"bulk-url-import": "Импортиране на рецепти от линк",
|
"bulk-url-import": "Импортиране на рецепти от линк",
|
||||||
"debug-scraper": "Отстраняване на грешки на скрейпъра",
|
"debug-scraper": "Отстраняване на грешки на скрейпъра",
|
||||||
"create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Създай рецепта като предоставиш име. Всички рецепти трябва да имат уникални имена.",
|
"create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Добави рецепта като предоставиш име. Всички рецепти трябва да имат уникални имена.",
|
||||||
"new-recipe-names-must-be-unique": "Името на рецептата трябва да бъде уникално",
|
"new-recipe-names-must-be-unique": "Името на рецептата трябва да бъде уникално",
|
||||||
"scrape-recipe": "Обхождане на рецепта",
|
"scrape-recipe": "Обхождане на рецепта",
|
||||||
"scrape-recipe-description": "Обходи рецепта по линк. Предоставете линк за сайт, който искате да бъде обходен. Mealie ще опита да обходи рецептата от този сайт и да я добави във Вашата колекция.",
|
"scrape-recipe-description": "Обходи рецепта по линк. Предоставете линк за сайт, който искате да бъде обходен. Mealie ще опита да обходи рецептата от този сайт и да я добави във Вашата колекция.",
|
||||||
"scrape-recipe-have-a-lot-of-recipes": "Have a lot of recipes you want to scrape at once?",
|
"scrape-recipe-have-a-lot-of-recipes": "Имате много рецепти, които искате да обходите наведнъж?",
|
||||||
"scrape-recipe-suggest-bulk-importer": "Try out the bulk importer",
|
"scrape-recipe-suggest-bulk-importer": "Пробвайте масовото импорторане",
|
||||||
"import-original-keywords-as-tags": "Импортирай оригиналните ключови думи като тагове",
|
"import-original-keywords-as-tags": "Добави оригиналните ключови думи като етикети",
|
||||||
"stay-in-edit-mode": "Остани в режим на редакция",
|
"stay-in-edit-mode": "Остани в режим на редакция",
|
||||||
"import-from-zip": "Импортирай от Zip",
|
"import-from-zip": "Импортирай от Zip",
|
||||||
"import-from-zip-description": "Импортирай рецепта, която е била експортирана от друга инстанция на Mealie.",
|
"import-from-zip-description": "Импортирай рецепта, която е била експортирана от друга инстанция на Mealie.",
|
||||||
"zip-files-must-have-been-exported-from-mealie": ".zip файловете трябва да бъдат експортирани от Mealie",
|
"zip-files-must-have-been-exported-from-mealie": ".zip файловете трябва да бъдат експортирани от Mealie",
|
||||||
"create-a-recipe-by-uploading-a-scan": "Създай рецепта като качиш сканирано копие.",
|
"create-a-recipe-by-uploading-a-scan": "Добави рецепта като качиш сканирано копие.",
|
||||||
"upload-a-png-image-from-a-recipe-book": "Качи png изображение от книга с рецепти",
|
"upload-a-png-image-from-a-recipe-book": "Качи png изображение от книга с рецепти",
|
||||||
"recipe-bulk-importer": "Масово импортиране на рецепти",
|
"recipe-bulk-importer": "Масово импортиране на рецепти",
|
||||||
"recipe-bulk-importer-description": "Масовото импортиране Ви позволява да импортиране множество рецепти наведнъж като постави сайтовете на опашка в бекенда и изпълненява задачата във фонов режим. Това може да бъде полезно когато първоначално мигрирате Mealie, или когато искате да импортиране голям брой рецепти наведнъж.",
|
"recipe-bulk-importer-description": "Масовото импортиране Ви позволява да импортиране множество рецепти наведнъж като постави сайтовете на опашка в бекенда и изпълненява задачата във фонов режим. Това може да бъде полезно когато първоначално мигрирате Mealie, или когато искате да импортиране голям брой рецепти наведнъж.",
|
||||||
"set-categories-and-tags": "Задай Категории и Тагове",
|
"set-categories-and-tags": "Задай категории и етикети",
|
||||||
"bulk-imports": "Масови импортирания",
|
"bulk-imports": "Масови импортирания",
|
||||||
"bulk-import-process-has-started": "Процеса на масово импортиране започна",
|
"bulk-import-process-has-started": "Процеса на масово импортиране започна",
|
||||||
"bulk-import-process-has-failed": "Процеса на масово импортиране се прекрати с грешка",
|
"bulk-import-process-has-failed": "Процеса на масово импортиране се прекрати с грешка",
|
||||||
@@ -558,7 +566,7 @@
|
|||||||
"upload-image": "Качване на изображение",
|
"upload-image": "Качване на изображение",
|
||||||
"screen-awake": "Запази екрана активен",
|
"screen-awake": "Запази екрана активен",
|
||||||
"remove-image": "Премахване на изображение",
|
"remove-image": "Премахване на изображение",
|
||||||
"nextStep": "Next step"
|
"nextStep": "Следваща стъпка"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"advanced-search": "Разширено търсене",
|
"advanced-search": "Разширено търсене",
|
||||||
@@ -573,7 +581,7 @@
|
|||||||
"search": "Търсене",
|
"search": "Търсене",
|
||||||
"search-mealie": "Търсене в Mealie (Натисни /)",
|
"search-mealie": "Търсене в Mealie (Натисни /)",
|
||||||
"search-placeholder": "Търсене...",
|
"search-placeholder": "Търсене...",
|
||||||
"tag-filter": "Филтриране на тагове",
|
"tag-filter": "Филтриране по етикет",
|
||||||
"search-hint": "Натисни '/'",
|
"search-hint": "Натисни '/'",
|
||||||
"advanced": "Разширени",
|
"advanced": "Разширени",
|
||||||
"auto-search": "Автоматично търсене",
|
"auto-search": "Автоматично търсене",
|
||||||
@@ -587,7 +595,7 @@
|
|||||||
"backup-created-at-response-export_path": "Резервно копие е създадено на {path}",
|
"backup-created-at-response-export_path": "Резервно копие е създадено на {path}",
|
||||||
"backup-deleted": "Резервното копие е изтрито",
|
"backup-deleted": "Резервното копие е изтрито",
|
||||||
"restore-success": "Успешно възстановяване",
|
"restore-success": "Успешно възстановяване",
|
||||||
"backup-tag": "Таг на резервното копие",
|
"backup-tag": "Етикет на резервното копие",
|
||||||
"create-heading": "Създай резервно копие",
|
"create-heading": "Създай резервно копие",
|
||||||
"delete-backup": "Изтрий резервно копие",
|
"delete-backup": "Изтрий резервно копие",
|
||||||
"error-creating-backup-see-log-file": "Грешка при създаването на резервно копие. Виж лог файла",
|
"error-creating-backup-see-log-file": "Грешка при създаването на резервно копие. Виж лог файла",
|
||||||
@@ -715,7 +723,7 @@
|
|||||||
"secure-site-success-text": "Сайтът е достъпен чрез localhost или https",
|
"secure-site-success-text": "Сайтът е достъпен чрез localhost или https",
|
||||||
"server-side-base-url": "Сървърен базов URL",
|
"server-side-base-url": "Сървърен базов URL",
|
||||||
"server-side-base-url-error-text": "„BASE_URL“ все още е стойността по подразбиране на API сървъра. Това ще причини проблеми с връзките за известия, генерирани на сървъра за имейли и др.",
|
"server-side-base-url-error-text": "„BASE_URL“ все още е стойността по подразбиране на API сървъра. Това ще причини проблеми с връзките за известия, генерирани на сървъра за имейли и др.",
|
||||||
"server-side-base-url-success-text": "URL адресът от страна на сървъра не съответства на стандартния",
|
"server-side-base-url-success-text": "URL адресът от страна на сървъра не съответства на зададения",
|
||||||
"ldap-ready": "Използва LDAP",
|
"ldap-ready": "Използва LDAP",
|
||||||
"ldap-ready-error-text": "Не всички LDAP стойности са конфигурирани. Това може да бъде игнорирано, ако не използвате LDAP удостоверяване.",
|
"ldap-ready-error-text": "Не всички LDAP стойности са конфигурирани. Това може да бъде игнорирано, ако не използвате LDAP удостоверяване.",
|
||||||
"ldap-ready-success-text": "Задължителните LDAP променливи са зададени.",
|
"ldap-ready-success-text": "Задължителните LDAP променливи са зададени.",
|
||||||
@@ -731,7 +739,7 @@
|
|||||||
"quantity": "Количество: {0}",
|
"quantity": "Количество: {0}",
|
||||||
"shopping-list": "Списък за пазаруване",
|
"shopping-list": "Списък за пазаруване",
|
||||||
"shopping-lists": "Списъци за пазаруване",
|
"shopping-lists": "Списъци за пазаруване",
|
||||||
"food": "Храна",
|
"food": "Продукт",
|
||||||
"note": "Бележка",
|
"note": "Бележка",
|
||||||
"label": "Етикет",
|
"label": "Етикет",
|
||||||
"linked-item-warning": "Елементът е добавен към една или повече рецепти. Редактиране на единиците или храните ще се отрази с непредвидими резултати когато добавяте или премахвате рецепта от списъка.",
|
"linked-item-warning": "Елементът е добавен към една или повече рецепти. Редактиране на единиците или храните ще се отрази с непредвидими резултати когато добавяте или премахвате рецепта от списъка.",
|
||||||
@@ -762,7 +770,7 @@
|
|||||||
"profile": "Профил",
|
"profile": "Профил",
|
||||||
"search": "Търсене",
|
"search": "Търсене",
|
||||||
"site-settings": "Настройки на сайта",
|
"site-settings": "Настройки на сайта",
|
||||||
"tags": "Тагове",
|
"tags": "Етикети",
|
||||||
"toolbox": "Инструменти",
|
"toolbox": "Инструменти",
|
||||||
"language": "Език",
|
"language": "Език",
|
||||||
"maintenance": "Профилактика",
|
"maintenance": "Профилактика",
|
||||||
@@ -783,17 +791,17 @@
|
|||||||
"welcome-to-mealie": "Добре дошли в Mealie! За да станете потребител на тази инстанция сте длъжни да имате валиден линк за покана. Ако не сте получили покана, тогава е невъзможно да се регистрирате. За да получите линк, свържете се с администратора на сайта."
|
"welcome-to-mealie": "Добре дошли в Mealie! За да станете потребител на тази инстанция сте длъжни да имате валиден линк за покана. Ако не сте получили покана, тогава е невъзможно да се регистрирате. За да получите линк, свържете се с администратора на сайта."
|
||||||
},
|
},
|
||||||
"tag": {
|
"tag": {
|
||||||
"tag-created": "Тагът е създаден",
|
"tag-created": "Етикетът беше добавен",
|
||||||
"tag-creation-failed": "Неуспешно създаване на таг",
|
"tag-creation-failed": "Неуспешно създаване на етикет",
|
||||||
"tag-deleted": "Тагът е изтрит",
|
"tag-deleted": "Етикетът беше изтрит",
|
||||||
"tag-deletion-failed": "Неуспешно изтриване на таг",
|
"tag-deletion-failed": "Неуспешно изтриване на етикет",
|
||||||
"tag-update-failed": "Неуспешно обновяване на таг",
|
"tag-update-failed": "Неуспешно обновяване на етикет",
|
||||||
"tag-updated": "Тагът е обновен",
|
"tag-updated": "Етикетът беше обновен",
|
||||||
"tags": "Тагове",
|
"tags": "Етикети",
|
||||||
"untagged-count": "Без таг {count}",
|
"untagged-count": "Без етикет {count}",
|
||||||
"create-a-tag": "Създаване на таг",
|
"create-a-tag": "Създаване на етикет",
|
||||||
"tag-name": "Име на тага",
|
"tag-name": "Име на етикета",
|
||||||
"tag": "Тагове"
|
"tag": "Етикет"
|
||||||
},
|
},
|
||||||
"tool": {
|
"tool": {
|
||||||
"tools": "Инструменти",
|
"tools": "Инструменти",
|
||||||
@@ -902,7 +910,7 @@
|
|||||||
"language-dialog": {
|
"language-dialog": {
|
||||||
"translated": "преведено",
|
"translated": "преведено",
|
||||||
"choose-language": "Избери Език",
|
"choose-language": "Избери Език",
|
||||||
"select-description": "Изберете език за Mealie. Тази настройка се прилага само за Вас, не и за други потребители.",
|
"select-description": "Изберете език за Mealie. Тази настройка засяга само текущия профил.",
|
||||||
"how-to-contribute-description": "Има ли нещо все още непреведено, с грешка в превода, или езикът Ви липсва в списъка? {read-the-docs-link} за да видите как да допринесете!",
|
"how-to-contribute-description": "Има ли нещо все още непреведено, с грешка в превода, или езикът Ви липсва в списъка? {read-the-docs-link} за да видите как да допринесете!",
|
||||||
"read-the-docs": "Прочетете документацията"
|
"read-the-docs": "Прочетете документацията"
|
||||||
},
|
},
|
||||||
@@ -910,12 +918,12 @@
|
|||||||
"foods": {
|
"foods": {
|
||||||
"merge-dialog-text": "Комбинирането на избраните храни ще обедини изходната храна и целевата храна в една единствена храна. Изходната храна ще бъде изтрита и всички препратки към изходната храна ще бъдат актуализирани, за да сочат към целевата храна.",
|
"merge-dialog-text": "Комбинирането на избраните храни ще обедини изходната храна и целевата храна в една единствена храна. Изходната храна ще бъде изтрита и всички препратки към изходната храна ще бъдат актуализирани, за да сочат към целевата храна.",
|
||||||
"merge-food-example": "Обединяване на {food1} с {food2}",
|
"merge-food-example": "Обединяване на {food1} с {food2}",
|
||||||
"seed-dialog-text": "Заредете базата данни с храни на базата на вашия местен език. Това ще създаде 200+ общи храни, които могат да се използват за организиране на вашата база данни. Храните се превеждат чрез усилия на общността.",
|
"seed-dialog-text": "Изтеглете базата данни с продукти на вашия местен език. Ще бъдат заредени 200+ продукта, които да използвате за организиране на вашата база данни. Имената на продуктите се превеждат от общността.",
|
||||||
"seed-dialog-warning": "Вече имате някои елементи във Вашата база данни. Това действие няма да съгласува дубликати, ще трябва да ги управлявате ръчно.",
|
"seed-dialog-warning": "Вече имате някои елементи във Вашата база данни. Това действие няма да съгласува дубликати, ще трябва да ги управлявате ръчно.",
|
||||||
"combine-food": "Комбинирай Храни",
|
"combine-food": "Комбинирай Храни",
|
||||||
"source-food": "Изходна храна",
|
"source-food": "Изходна храна",
|
||||||
"target-food": "Целева храна",
|
"target-food": "Целева храна",
|
||||||
"create-food": "Създай храна",
|
"create-food": "Създай продукт",
|
||||||
"food-label": "Заглавие на храната",
|
"food-label": "Заглавие на храната",
|
||||||
"edit-food": "Редактирай храна",
|
"edit-food": "Редактирай храна",
|
||||||
"food-data": "Данни за храните",
|
"food-data": "Данни за храните",
|
||||||
@@ -923,7 +931,7 @@
|
|||||||
"example-food-plural": "пример: Домати"
|
"example-food-plural": "пример: Домати"
|
||||||
},
|
},
|
||||||
"units": {
|
"units": {
|
||||||
"seed-dialog-text": "Заредете базата данни с общи единици въз основа на Вашия местен език.",
|
"seed-dialog-text": "Заредете базата данни с мерни единици на Вашия местен език.",
|
||||||
"combine-unit-description": "Комбинирането на избраните единици ще обедини единицата източник и целевата единица в една единица. {source-unit-will-be-deleted} и всички препратки към изходната единица ще бъдат актуализирани, за да сочат към целевата единица.",
|
"combine-unit-description": "Комбинирането на избраните единици ще обедини единицата източник и целевата единица в една единица. {source-unit-will-be-deleted} и всички препратки към изходната единица ще бъдат актуализирани, за да сочат към целевата единица.",
|
||||||
"combine-unit": "Комбинирай мерни единици",
|
"combine-unit": "Комбинирай мерни единици",
|
||||||
"source-unit": "Изходна мярна единица",
|
"source-unit": "Изходна мярна единица",
|
||||||
@@ -945,7 +953,7 @@
|
|||||||
"example-unit-abbreviation-plural": "пример: ч.л.-ки"
|
"example-unit-abbreviation-plural": "пример: ч.л.-ки"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"seed-dialog-text": "Заредете базата данни с общи етикети въз основа на Вашия местен език.",
|
"seed-dialog-text": "Заредете базата данни с етикети на Вашия местен език.",
|
||||||
"edit-label": "Редактиране на етикет",
|
"edit-label": "Редактиране на етикет",
|
||||||
"new-label": "Нов етикет",
|
"new-label": "Нов етикет",
|
||||||
"labels": "Етикети"
|
"labels": "Етикети"
|
||||||
@@ -962,10 +970,10 @@
|
|||||||
"recipe-columns": "Колони на рецептата",
|
"recipe-columns": "Колони на рецептата",
|
||||||
"data-exports-description": "Този раздел предоставя връзки към налични експортирания, които са готови за изтегляне. Тези експорти изтичат, така че не забравяйте да ги изтеглите, докато все още са налични.",
|
"data-exports-description": "Този раздел предоставя връзки към налични експортирания, които са готови за изтегляне. Тези експорти изтичат, така че не забравяйте да ги изтеглите, докато все още са налични.",
|
||||||
"data-exports": "Експорти на данни",
|
"data-exports": "Експорти на данни",
|
||||||
"tag": "Таг",
|
"tag": "Етикет",
|
||||||
"categorize": "Категоризиране",
|
"categorize": "Категоризиране",
|
||||||
"update-settings": "Обнови настройките",
|
"update-settings": "Обнови настройките",
|
||||||
"tag-recipes": "Тагове на рецепти",
|
"tag-recipes": "Етикети на рецепти",
|
||||||
"categorize-recipes": "Категоризирай рецепти",
|
"categorize-recipes": "Категоризирай рецепти",
|
||||||
"export-recipes": "Експортирай рецепти",
|
"export-recipes": "Експортирай рецепти",
|
||||||
"delete-recipes": "Изтрий рецепти",
|
"delete-recipes": "Изтрий рецепти",
|
||||||
@@ -973,8 +981,8 @@
|
|||||||
},
|
},
|
||||||
"create-alias": "Създаване на псевдоним",
|
"create-alias": "Създаване на псевдоним",
|
||||||
"manage-aliases": "Управление на псевдоними",
|
"manage-aliases": "Управление на псевдоними",
|
||||||
"seed-data": "Сийд на данни",
|
"seed-data": "Зареждане на данни",
|
||||||
"seed": "Сийд",
|
"seed": "Зареждане",
|
||||||
"data-management": "Управление на данни",
|
"data-management": "Управление на данни",
|
||||||
"data-management-description": "Изберете кой набор от данни искате да промените.",
|
"data-management-description": "Изберете кой набор от данни искате да промените.",
|
||||||
"select-data": "Изберете данни",
|
"select-data": "Изберете данни",
|
||||||
@@ -987,9 +995,9 @@
|
|||||||
"category-data": "Категория за данните"
|
"category-data": "Категория за данните"
|
||||||
},
|
},
|
||||||
"tags": {
|
"tags": {
|
||||||
"new-tag": "Нов таг",
|
"new-tag": "Нов етикет",
|
||||||
"edit-tag": "Редакция на таг",
|
"edit-tag": "Редакция на етикет",
|
||||||
"tag-data": "Данни на тага"
|
"tag-data": "Данни на етикета"
|
||||||
},
|
},
|
||||||
"tools": {
|
"tools": {
|
||||||
"new-tool": "Нов инструмент",
|
"new-tool": "Нов инструмент",
|
||||||
@@ -1006,7 +1014,7 @@
|
|||||||
"group-details": "Подробности за групата",
|
"group-details": "Подробности за групата",
|
||||||
"group-details-description": "Преди да създадете акаунт, ще трябва да създадете група. Вашата група ще съдържа само Вас, но ще можете да поканите други по-късно. Членовете във вашата група могат да споделят планове за хранене, списъци за пазаруване, рецепти и други!",
|
"group-details-description": "Преди да създадете акаунт, ще трябва да създадете група. Вашата група ще съдържа само Вас, но ще можете да поканите други по-късно. Членовете във вашата група могат да споделят планове за хранене, списъци за пазаруване, рецепти и други!",
|
||||||
"use-seed-data": "Използвай предварителни данни",
|
"use-seed-data": "Използвай предварителни данни",
|
||||||
"use-seed-data-description": "Mealie се доставя с колекция от храни, мерни единици и етикети, които могат да се използват за попълване на Вашата група с полезни данни за организиране на вашите рецепти.",
|
"use-seed-data-description": "Mealie се доставя с колекция от продукти, мерни единици и етикети за попълване на Вашата група с полезни данни за организиране на рецептите.",
|
||||||
"account-details": "Подробни данни за акаунта"
|
"account-details": "Подробни данни за акаунта"
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
@@ -1129,7 +1137,7 @@
|
|||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"welcome-user": "👋 Добре дошъл(а), {0}",
|
"welcome-user": "👋 Добре дошъл(а), {0}",
|
||||||
"description": "Управлявай твоят профил, рецепти и настройки на групата.",
|
"description": "Настройки на профил, рецепти и настройки на групата.",
|
||||||
"get-invite-link": "Вземи линк за покана",
|
"get-invite-link": "Вземи линк за покана",
|
||||||
"get-public-link": "Вземи публичен линк",
|
"get-public-link": "Вземи публичен линк",
|
||||||
"account-summary": "Обобщение на акаунта",
|
"account-summary": "Обобщение на акаунта",
|
||||||
@@ -1141,19 +1149,19 @@
|
|||||||
"personal": "Лични",
|
"personal": "Лични",
|
||||||
"personal-description": "Това са настройки, които са лични за Вас. Промените тук няма да засегнат други потребители",
|
"personal-description": "Това са настройки, които са лични за Вас. Промените тук няма да засегнат други потребители",
|
||||||
"user-settings": "Потребителски настройки",
|
"user-settings": "Потребителски настройки",
|
||||||
"user-settings-description": "Управлявайте предпочитанията си, променяйте паролата си и актуализирайте имейла си",
|
"user-settings-description": "Нстройки на предпочитанията, смяна на парола и актуализация на имей адрес",
|
||||||
"api-tokens-description": "Управлявайте вашите API токени за достъп от външни приложения",
|
"api-tokens-description": "Управление на API токени за достъп от външни приложения",
|
||||||
"group-description": "Тези елементи се споделят във вашата група. Редактирането на един от тях ще го промени за цялата група!",
|
"group-description": "Тези елементи се споделят във вашата група. Редактирането на един от тях ще го промени за цялата група!",
|
||||||
"group-settings": "Настройки на групата",
|
"group-settings": "Настройки на групата",
|
||||||
"group-settings-description": "Управлявайте общите си групови настройки като план за хранене и настройки за поверителност.",
|
"group-settings-description": "Общи групови настройки като седмично меню и настройки за поверителност.",
|
||||||
"cookbooks-description": "Управлявайте колекция от категории на рецепти и генерирайте страници за тях.",
|
"cookbooks-description": "Управление на категории на рецепти и генериране на съответните страници.",
|
||||||
"members": "Участници",
|
"members": "Участници",
|
||||||
"members-description": "Вижте кой е във Вашата група и управлявайте техните права.",
|
"members-description": "Списък на потребителите в групата и управление на техните права.",
|
||||||
"webhooks-description": "Настройте webhooks, които се задействат в дните, в които имате планиран план за хранене.",
|
"webhooks-description": "Настройте webhooks, които се задействат в дните, в които имате планиран план за хранене.",
|
||||||
"notifiers": "Уведомители",
|
"notifiers": "Уведомители",
|
||||||
"notifiers-description": "Настройте имейл и push известия, които се задействат при конкретни събития.",
|
"notifiers-description": "Настройте имейл и push известия, които се задействат при конкретни събития.",
|
||||||
"manage-data": "Управление на данни",
|
"manage-data": "Управление на данни",
|
||||||
"manage-data-description": "Управлявайте вашата храна и единици (очаквайте още опции скоро)",
|
"manage-data-description": "Настройки на продукти и мерни единици (очаквайте добавяне на още възможности)",
|
||||||
"data-migrations": "Миграция на данни",
|
"data-migrations": "Миграция на данни",
|
||||||
"data-migrations-description": "Мигрирайте вашите съществуващи данни от други приложения като Nextcloud Recipes и Chowdown",
|
"data-migrations-description": "Мигрирайте вашите съществуващи данни от други приложения като Nextcloud Recipes и Chowdown",
|
||||||
"email-sent": "Имейлът е изпратен",
|
"email-sent": "Имейлът е изпратен",
|
||||||
@@ -1173,15 +1181,17 @@
|
|||||||
},
|
},
|
||||||
"cookbook": {
|
"cookbook": {
|
||||||
"cookbooks": "Готварски книги",
|
"cookbooks": "Готварски книги",
|
||||||
"description": "Готварските книги са друг начин за организиране на рецепти чрез създаване на напречни сечения на рецепти и тагове. Създаването на готварска книга ще добави запис към страничната лента и всички рецепти с избраните тагове и категории ще бъдат показани в готварската книга.",
|
"description": "Готварските книги са друг начин за организиране на рецепти чрез създаване на напречни сечения на рецепти и етикети. Създаването на готварска книга ще добави запис към страничната лента и всички рецепти с избраните категории и етикети ще бъдат показани в готварската книга.",
|
||||||
"public-cookbook": "Публична книга с рецепти",
|
"public-cookbook": "Публична книга с рецепти",
|
||||||
"public-cookbook-description": "Публичните готварски книги могат да се споделят с потребители, които не са в Mealie, и ще се показват на страницата на вашите групи.",
|
"public-cookbook-description": "Публичните готварски книги могат да се споделят с потребители, които не са в Mealie, и ще се показват на страницата на вашите групи.",
|
||||||
"filter-options": "Опции на филтъра",
|
"filter-options": "Опции на филтъра",
|
||||||
"filter-options-description": "Когато е избрано изискване на всички, готварската книга ще включва само рецепти, които имат всички избрани елементи. Това се отнася за всяко подмножество от селектори, а не за напречно сечение на избраните елементи.",
|
"filter-options-description": "Когато е избрано изискване на всички, готварската книга ще включва само рецепти, които имат всички избрани елементи. Това се отнася за всяко подмножество от селектори, а не за напречно сечение на избраните елементи.",
|
||||||
"require-all-categories": "Изискване на всички категории",
|
"require-all-categories": "Изискване на всички категории",
|
||||||
"require-all-tags": "Изискване на всички тагове",
|
"require-all-tags": "Включване на всички етикети",
|
||||||
"require-all-tools": "Изискване на всички инструменти",
|
"require-all-tools": "Изискване на всички инструменти",
|
||||||
"cookbook-name": "Име на книгата с рецепти",
|
"cookbook-name": "Име на книгата с рецепти",
|
||||||
"cookbook-with-name": "Книга с рецепти {0}"
|
"cookbook-with-name": "Книга с рецепти {0}",
|
||||||
|
"create-a-cookbook": "Създай Готварска книга",
|
||||||
|
"cookbook": "Готварска книга"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||