mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-25 17:23:11 -05:00
Compare commits
668 Commits
Kuchenpira
...
v3.5.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cf7e37ada | ||
|
|
930c92365d | ||
|
|
6f1fee5511 | ||
|
|
f5de126d86 | ||
|
|
725dae41b1 | ||
|
|
39e919526a | ||
|
|
1978ad2c96 | ||
|
|
23e8dc1941 | ||
|
|
96b408a661 | ||
|
|
20a9a94770 | ||
|
|
b280e2d1a0 | ||
|
|
735162d042 | ||
|
|
60d9294861 | ||
|
|
ff42964537 | ||
|
|
bb67d993a0 | ||
|
|
7bb0f0801a | ||
|
|
3a4875a54f | ||
|
|
0371874670 | ||
|
|
3d177566ed | ||
|
|
14e87918fb | ||
|
|
ac75b0254d | ||
|
|
7f2927600b | ||
|
|
5e8c4a6cee | ||
|
|
a460c32674 | ||
|
|
973cd5ab02 | ||
|
|
ac355c1071 | ||
|
|
3a617cd3c3 | ||
|
|
3c874c2f85 | ||
|
|
fb3be73163 | ||
|
|
14b783852e | ||
|
|
75616d66b8 | ||
|
|
01713b0416 | ||
|
|
123a8b99f8 | ||
|
|
6732fcd696 | ||
|
|
5fcbfbf361 | ||
|
|
1318998bc9 | ||
|
|
0947212271 | ||
|
|
92ac5c6253 | ||
|
|
5f96f4b47f | ||
|
|
dbcd430425 | ||
|
|
4c9164594b | ||
|
|
e5a13f8b43 | ||
|
|
726ad10c7e | ||
|
|
df53310f2e | ||
|
|
82bf5c1bae | ||
|
|
c70a63f0ff | ||
|
|
14bfa6bcae | ||
|
|
adbafef157 | ||
|
|
62d52f53e4 | ||
|
|
4370319fec | ||
|
|
15908d190d | ||
|
|
fcb909e072 | ||
|
|
8e532af4d9 | ||
|
|
831cb6dd17 | ||
|
|
089bb24c0f | ||
|
|
107dfc34de | ||
|
|
144d4caea6 | ||
|
|
b3db81b9a4 | ||
|
|
dc2bbdc494 | ||
|
|
8f17a08923 | ||
|
|
f6209bff54 | ||
|
|
33865285d1 | ||
|
|
e226b9b1d5 | ||
|
|
201c63d1e4 | ||
|
|
a242f567ad | ||
|
|
67ead2e8a1 | ||
|
|
7b273b77e2 | ||
|
|
b4cd095360 | ||
|
|
a9bb27c782 | ||
|
|
9df1523911 | ||
|
|
0c8a1ae608 | ||
|
|
7d54404bf0 | ||
|
|
8bbe70d245 | ||
|
|
6c87f7fe33 | ||
|
|
7e168eb75b | ||
|
|
64d481b4fc | ||
|
|
a9926557bc | ||
|
|
2a908c0dd2 | ||
|
|
c64a0dc769 | ||
|
|
7ce9c35ef5 | ||
|
|
0acca2021d | ||
|
|
5de0b48aa9 | ||
|
|
ffe199c083 | ||
|
|
215a18be42 | ||
|
|
a1b065e5d1 | ||
|
|
d660d89a1b | ||
|
|
ade1f797a9 | ||
|
|
192872b9ec | ||
|
|
25ebcb1a05 | ||
|
|
89d95ca5e1 | ||
|
|
b705652af3 | ||
|
|
b0c78de2da | ||
|
|
c4b1f9fd01 | ||
|
|
2b0d8227f4 | ||
|
|
42517e9f8a | ||
|
|
37c97c8aba | ||
|
|
64d36a2608 | ||
|
|
563defe074 | ||
|
|
f5ffb760d3 | ||
|
|
3118a0c0cf | ||
|
|
444beb68f9 | ||
|
|
49a97ebc0e | ||
|
|
6f682b742e | ||
|
|
32d4d22bb8 | ||
|
|
71d86489f4 | ||
|
|
a95eaf3d2e | ||
|
|
414af989e7 | ||
|
|
b7b191a5ee | ||
|
|
5620370ade | ||
|
|
d333d47e34 | ||
|
|
b34b1c9be3 | ||
|
|
8c5010148d | ||
|
|
a17b0e329e | ||
|
|
8ab69a7d7a | ||
|
|
f4ecf74b91 | ||
|
|
ba9d816f64 | ||
|
|
6895b49543 | ||
|
|
fffe7b05e0 | ||
|
|
1271e0e49b | ||
|
|
478054b724 | ||
|
|
57d259a7a3 | ||
|
|
a4a6d4dfb1 | ||
|
|
f7b4f79312 | ||
|
|
434d312f7c | ||
|
|
bda460b49e | ||
|
|
d3e1c48655 | ||
|
|
b2a3430f2c | ||
|
|
3d792d9333 | ||
|
|
2e028d7e12 | ||
|
|
c63932e8b3 | ||
|
|
3ba2227bc7 | ||
|
|
67af391c6b | ||
|
|
70ae0dac25 | ||
|
|
e15a9c3c9f | ||
|
|
9d40d60b3b | ||
|
|
e2760f7247 | ||
|
|
83bf21b947 | ||
|
|
d1824affff | ||
|
|
4827e1092f | ||
|
|
7db767b075 | ||
|
|
afdd0b15dc | ||
|
|
37c9166a77 | ||
|
|
ba0b9d4cd9 | ||
|
|
9fd99a86b8 | ||
|
|
824603a578 | ||
|
|
e3f120c680 | ||
|
|
d16a10440d | ||
|
|
ecdf7de386 | ||
|
|
0e10ed8461 | ||
|
|
1684169e7b | ||
|
|
3d9f2bef82 | ||
|
|
a722b05fb5 | ||
|
|
187e83eeb5 | ||
|
|
f3cc51190c | ||
|
|
33aedd6904 | ||
|
|
ea9a25a891 | ||
|
|
3a237258a1 | ||
|
|
d29de8e679 | ||
|
|
79367872ac | ||
|
|
f058dec27b | ||
|
|
c87acf54db | ||
|
|
84c144e40f | ||
|
|
474cf299cd | ||
|
|
1cababc5a5 | ||
|
|
8705bcf195 | ||
|
|
bdb511c1c8 | ||
|
|
c9f3f65f36 | ||
|
|
3ec55f0e48 | ||
|
|
7d43c7c7a2 | ||
|
|
c710e9d3f5 | ||
|
|
0313e6b3b8 | ||
|
|
24b890136d | ||
|
|
4b67554b36 | ||
|
|
679a42a7cc | ||
|
|
4dfc32a314 | ||
|
|
96acc6fc4b | ||
|
|
249c9e8f23 | ||
|
|
7413185300 | ||
|
|
6168ea0150 | ||
|
|
f7ba7862d4 | ||
|
|
cec6d2c5ec | ||
|
|
b27977fbdf | ||
|
|
2a60b330ac | ||
|
|
72ec5bd13e | ||
|
|
bb45cbb0a2 | ||
|
|
c929a03b57 | ||
|
|
9e5a54477f | ||
|
|
078b4563b3 | ||
|
|
a9090bc2bd | ||
|
|
cb8c1423c5 | ||
|
|
f6a1b5f4eb | ||
|
|
7623b72c4c | ||
|
|
17d40e34df | ||
|
|
bade6968a3 | ||
|
|
92a142125f | ||
|
|
d39c2a2874 | ||
|
|
324de7fb10 | ||
|
|
c4544ea042 | ||
|
|
a5dda74812 | ||
|
|
fd7e58e40c | ||
|
|
5e42841a7d | ||
|
|
ae9306b8c2 | ||
|
|
7f0c5cbcc4 | ||
|
|
a7d8bcc6ba | ||
|
|
b94ef78a12 | ||
|
|
db2c14093d | ||
|
|
9a0525c3a0 | ||
|
|
a2e5826da0 | ||
|
|
d4f4ba0c8d | ||
|
|
8cd5835dd8 | ||
|
|
7aa131b326 | ||
|
|
af264bd288 | ||
|
|
72388e8bcf | ||
|
|
c0afef46d6 | ||
|
|
f90665cce9 | ||
|
|
942ac741cd | ||
|
|
1d3a7e8d62 | ||
|
|
5e85fc409e | ||
|
|
2c20e96ede | ||
|
|
608fc39747 | ||
|
|
ed2f40cd6a | ||
|
|
a080cdb432 | ||
|
|
83101e3ed5 | ||
|
|
5d90997ace | ||
|
|
c78c6cf926 | ||
|
|
e26191d116 | ||
|
|
3774f68393 | ||
|
|
c46c412bf5 | ||
|
|
aa9e61a16f | ||
|
|
b2f8d63f33 | ||
|
|
72b47a1103 | ||
|
|
29e150d547 | ||
|
|
e9ae6d86a4 | ||
|
|
f799938373 | ||
|
|
e5fff4ec5c | ||
|
|
192e531c1f | ||
|
|
45e710ee72 | ||
|
|
be579ed664 | ||
|
|
fe953896f8 | ||
|
|
decf7cb307 | ||
|
|
d396a8fdc2 | ||
|
|
a3ef49f559 | ||
|
|
41e8458389 | ||
|
|
18dc2fc6a8 | ||
|
|
6355b3c8db | ||
|
|
3ac8af138f | ||
|
|
2b3803fb2e | ||
|
|
6a80e70486 | ||
|
|
f1dc854770 | ||
|
|
581aa929bd | ||
|
|
461e51bd22 | ||
|
|
1cdf43c599 | ||
|
|
6bfbc7ca0a | ||
|
|
608dbaa4c1 | ||
|
|
89c1e007cb | ||
|
|
fb5db583d2 | ||
|
|
bef3045e65 | ||
|
|
ff958a5015 | ||
|
|
37789c342e | ||
|
|
b6b8bea925 | ||
|
|
60834178ba | ||
|
|
0375a0bd5a | ||
|
|
3361f9a7c3 | ||
|
|
0883ef05ab | ||
|
|
c4eb020a66 | ||
|
|
600f407b4f | ||
|
|
6f92a829d6 | ||
|
|
6b11ff5128 | ||
|
|
29fdad1574 | ||
|
|
54b3df105c | ||
|
|
9a3303b06c | ||
|
|
c17accd82b | ||
|
|
18f7e8d935 | ||
|
|
6d2936cab6 | ||
|
|
cc2e33a254 | ||
|
|
eee6f8113c | ||
|
|
bd10cb8cd8 | ||
|
|
d03081c4e6 | ||
|
|
64d865bf7e | ||
|
|
27efda2772 | ||
|
|
81986e63b8 | ||
|
|
42eef17cfb | ||
|
|
1f724856b1 | ||
|
|
618ea06b7a | ||
|
|
ca2039ae35 | ||
|
|
15ecab86d1 | ||
|
|
aa164424d3 | ||
|
|
99acb349bd | ||
|
|
894162a669 | ||
|
|
347af7d417 | ||
|
|
cac1699aeb | ||
|
|
d577966bfb | ||
|
|
c663efde09 | ||
|
|
9e568a1182 | ||
|
|
fc38ef2ba9 | ||
|
|
323a8100db | ||
|
|
01d3d5d325 | ||
|
|
3f52c66f02 | ||
|
|
566f744220 | ||
|
|
561b50ba45 | ||
|
|
4228c9e753 | ||
|
|
2a5c3f6457 | ||
|
|
389f8b4279 | ||
|
|
f2b71e981e | ||
|
|
ec7e3a5103 | ||
|
|
6f0183cc4b | ||
|
|
12d38c89ea | ||
|
|
492c9a948d | ||
|
|
a808c8a18b | ||
|
|
0c6483aefa | ||
|
|
1568c33c37 | ||
|
|
5d0cf8422b | ||
|
|
d322abc1b4 | ||
|
|
c34180632b | ||
|
|
c74f016f66 | ||
|
|
e11ee47109 | ||
|
|
a044ca2779 | ||
|
|
f584fa33f4 | ||
|
|
4f33c3a44f | ||
|
|
394c271294 | ||
|
|
cb3eb59501 | ||
|
|
c41a4a52ed | ||
|
|
6cbc308d83 | ||
|
|
db765b0693 | ||
|
|
184262df17 | ||
|
|
df10cf8211 | ||
|
|
c91d216fe9 | ||
|
|
1c23d855ae | ||
|
|
fd966e5843 | ||
|
|
f9f1285cb3 | ||
|
|
dbf1202f69 | ||
|
|
b3f12ee7ac | ||
|
|
556dc699c7 | ||
|
|
481ce92d84 | ||
|
|
1df26aeb99 | ||
|
|
5c4694c3d8 | ||
|
|
9b2b8091ac | ||
|
|
ad60b4445b | ||
|
|
040fb48807 | ||
|
|
f8ce5b3afb | ||
|
|
31530a68e1 | ||
|
|
51ca65e3c3 | ||
|
|
19eae21b00 | ||
|
|
34e4480f08 | ||
|
|
ad875c8fd5 | ||
|
|
6ec7baa2f1 | ||
|
|
f39929a905 | ||
|
|
4b69e5b33a | ||
|
|
4c2b559e73 | ||
|
|
178e038c79 | ||
|
|
0b3fe2c8da | ||
|
|
d4e62c5ab6 | ||
|
|
0f591fd273 | ||
|
|
d271252ecc | ||
|
|
4d211e236d | ||
|
|
7926812136 | ||
|
|
f37d8e488f | ||
|
|
56df696546 | ||
|
|
d3436a5ca8 | ||
|
|
9e46c57e78 | ||
|
|
79b6be8550 | ||
|
|
bd9e654de7 | ||
|
|
f149bcc98f | ||
|
|
0095cc7153 | ||
|
|
f276ecd96e | ||
|
|
87bfdb633b | ||
|
|
e47f386643 | ||
|
|
5c9fd22f11 | ||
|
|
30ee276e52 | ||
|
|
50c8e9be79 | ||
|
|
c4fdcec85f | ||
|
|
f7b32debbb | ||
|
|
1f1cc10f21 | ||
|
|
2ae3427a59 | ||
|
|
357f843df5 | ||
|
|
3973fe99a1 | ||
|
|
8d51c14d24 | ||
|
|
f2af8a0bc1 | ||
|
|
c130e8e92d | ||
|
|
ea099a743b | ||
|
|
d21e685219 | ||
|
|
04d0144369 | ||
|
|
8a8580e83c | ||
|
|
6b6c167153 | ||
|
|
e1c4a703a2 | ||
|
|
591c96e52b | ||
|
|
b157c7034f | ||
|
|
245ca5fe3b | ||
|
|
efc0d31724 | ||
|
|
4b7f7b4b8a | ||
|
|
7aee575352 | ||
|
|
2ef5b0d389 | ||
|
|
387a12cf1a | ||
|
|
f26e74f0f2 | ||
|
|
f2b6512eb1 | ||
|
|
93f3762016 | ||
|
|
620465f14c | ||
|
|
a132b83f1b | ||
|
|
5f522b5324 | ||
|
|
bd0aed06ce | ||
|
|
f9f88fb8a4 | ||
|
|
eefe613aaf | ||
|
|
d6d247f1f8 | ||
|
|
3b74ddd9ad | ||
|
|
eec4eeb76a | ||
|
|
73a9e470c3 | ||
|
|
b578d8d4f7 | ||
|
|
7a43546eeb | ||
|
|
e7c310934d | ||
|
|
07d5928f18 | ||
|
|
fa5bc17ed8 | ||
|
|
f8cb80ed7f | ||
|
|
066cd13e13 | ||
|
|
a087760d53 | ||
|
|
675ac9c32b | ||
|
|
d7191983bd | ||
|
|
14b3fd524f | ||
|
|
38ed0d0532 | ||
|
|
3b48f73b91 | ||
|
|
ae7e7942e3 | ||
|
|
ec1eddc06d | ||
|
|
2781771f6b | ||
|
|
9b35a2f904 | ||
|
|
a323350915 | ||
|
|
9b94cfdd24 | ||
|
|
2818ff56ee | ||
|
|
d728df7d40 | ||
|
|
a531eb649e | ||
|
|
fb4aa2b713 | ||
|
|
0df9d4b958 | ||
|
|
99523c70ed | ||
|
|
5c7a4fb861 | ||
|
|
436a24f8b2 | ||
|
|
93534de638 | ||
|
|
f29a11d20e | ||
|
|
880bd91f4a | ||
|
|
8e68782ff6 | ||
|
|
c1e5937ff3 | ||
|
|
6d1e39f871 | ||
|
|
5b92e969dc | ||
|
|
992a74499d | ||
|
|
4744e6371a | ||
|
|
00a03559ff | ||
|
|
a1a45078fb | ||
|
|
88d0b466ce | ||
|
|
e638df37d1 | ||
|
|
44b180f5c0 | ||
|
|
3feb11d138 | ||
|
|
41633be864 | ||
|
|
ce8fa2194b | ||
|
|
7d1c5ad14b | ||
|
|
6274a3dd39 | ||
|
|
a1e8e1aa20 | ||
|
|
b4aff0d8e9 | ||
|
|
e07df8fc43 | ||
|
|
9413e403b4 | ||
|
|
6eab88d44c | ||
|
|
754878eb63 | ||
|
|
c65456c646 | ||
|
|
dd55d23a37 | ||
|
|
aafed68964 | ||
|
|
108ac40b22 | ||
|
|
d2e9c04af1 | ||
|
|
3a1f58037d | ||
|
|
2731fb4a01 | ||
|
|
769db7202d | ||
|
|
644e871ec1 | ||
|
|
8987faa4f6 | ||
|
|
c237b33126 | ||
|
|
7098b67784 | ||
|
|
2d21d00651 | ||
|
|
198d5e4e06 | ||
|
|
98fc333df3 | ||
|
|
c0fb27f979 | ||
|
|
9ec1599427 | ||
|
|
9cfc54b1f5 | ||
|
|
40d2ac9a6b | ||
|
|
44db525049 | ||
|
|
d737cb3e14 | ||
|
|
1034d87a99 | ||
|
|
1243e6804c | ||
|
|
8b9e80358b | ||
|
|
2bae6e9d02 | ||
|
|
6b98a7cd74 | ||
|
|
e0238eb3a2 | ||
|
|
5adb7662c4 | ||
|
|
4e6a7a09ff | ||
|
|
719c7c9f6b | ||
|
|
7331007f30 | ||
|
|
ea329a6b71 | ||
|
|
e1a04ba673 | ||
|
|
63a4d4c801 | ||
|
|
5cf3e2565a | ||
|
|
9e1fe618ba | ||
|
|
691300e481 | ||
|
|
939588f54c | ||
|
|
2d8f491666 | ||
|
|
50754ad012 | ||
|
|
04eca1b992 | ||
|
|
aad7dc1abd | ||
|
|
2f19d31d1b | ||
|
|
095b92c29a | ||
|
|
49c704a4b1 | ||
|
|
c15a4f786b | ||
|
|
6e33878e4f | ||
|
|
5ca004802d | ||
|
|
68115cbf2f | ||
|
|
2b4bc8a662 | ||
|
|
fc801c9da4 | ||
|
|
f99b305dc3 | ||
|
|
b0b3d7e5e5 | ||
|
|
eedd2204a6 | ||
|
|
1ccc67774a | ||
|
|
6d98041ec8 | ||
|
|
c24cfb8096 | ||
|
|
ca41bc8d5c | ||
|
|
da3271f33f | ||
|
|
50a986f331 | ||
|
|
f72ebed0dc | ||
|
|
0c534ad9d4 | ||
|
|
9cce0f65aa | ||
|
|
c9e22892a6 | ||
|
|
e794c6b525 | ||
|
|
abc37f258d | ||
|
|
c2fda0d85a | ||
|
|
437a6ae526 | ||
|
|
9f5de0bd5d | ||
|
|
4bf963b14c | ||
|
|
7092d85a53 | ||
|
|
83fd320920 | ||
|
|
28e2666c17 | ||
|
|
62c7e2d2fb | ||
|
|
6540bfacfe | ||
|
|
47eb1ebbb1 | ||
|
|
31f90c79c0 | ||
|
|
3b1edf67fc | ||
|
|
781bbecc7b | ||
|
|
15f06b5378 | ||
|
|
95fa0af28a | ||
|
|
084f99b0de | ||
|
|
2fb5dac966 | ||
|
|
51ec02bdb2 | ||
|
|
1a1fe0a442 | ||
|
|
b0b88d361f | ||
|
|
b4a9c472e5 | ||
|
|
bcc038091a | ||
|
|
9e0db03f8c | ||
|
|
af274bf476 | ||
|
|
ca9d5677b8 | ||
|
|
07483a13ff | ||
|
|
d412271b0b | ||
|
|
cea3ddc883 | ||
|
|
c965d12bf1 | ||
|
|
181aebf424 | ||
|
|
b77ff9c341 | ||
|
|
93cec24f26 | ||
|
|
a2a0ad1af0 | ||
|
|
969a3c9005 | ||
|
|
a09601f051 | ||
|
|
d6110f1a94 | ||
|
|
1562437b98 | ||
|
|
e2eb754cf2 | ||
|
|
3a4222c6c1 | ||
|
|
2673834a9f | ||
|
|
c24d532608 | ||
|
|
89ab7fac25 | ||
|
|
78b55c0b98 | ||
|
|
ac984a2d04 | ||
|
|
079cfe7fe0 | ||
|
|
4a9095fcbb | ||
|
|
384bb7480f | ||
|
|
69488bd6df | ||
|
|
038fbd38ef | ||
|
|
1697d6299e | ||
|
|
b87edc823a | ||
|
|
cacb197aa8 | ||
|
|
5d58c93331 | ||
|
|
104c9b36a5 | ||
|
|
b68c96c348 | ||
|
|
b577cf5520 | ||
|
|
431638c1ed | ||
|
|
a4871b65eb | ||
|
|
582974b265 | ||
|
|
22fdb32f61 | ||
|
|
649013a028 | ||
|
|
14de1410ae | ||
|
|
03bc87d3a8 | ||
|
|
bb7885543e | ||
|
|
404a4cfa9d | ||
|
|
63a5c0076a | ||
|
|
a4ea5ba10d | ||
|
|
fc6b239343 | ||
|
|
9185cd8df1 | ||
|
|
f0a9d5333d | ||
|
|
7bb84d504a | ||
|
|
dad2712fe9 | ||
|
|
8e7e3e21ed | ||
|
|
af3057951d | ||
|
|
2f3ef738c4 | ||
|
|
44ee1440e2 | ||
|
|
c4aaf1a8c3 | ||
|
|
e093a93189 | ||
|
|
51c92a1e35 | ||
|
|
84629c540e | ||
|
|
28b3ba6506 | ||
|
|
a6ce140e60 | ||
|
|
4784672113 | ||
|
|
9db31ca125 | ||
|
|
972b588250 | ||
|
|
57ae31d231 | ||
|
|
7398b2784a | ||
|
|
c13c0868ae | ||
|
|
a652830a26 | ||
|
|
1f34571820 | ||
|
|
4e16273f00 | ||
|
|
d110f21d37 | ||
|
|
6caa74254f | ||
|
|
66bc4c25ec | ||
|
|
89bed4d675 | ||
|
|
25fbdd6523 | ||
|
|
7e64ce2767 | ||
|
|
62dabe2c18 | ||
|
|
3742c4e86c | ||
|
|
98da2cadc6 | ||
|
|
8360829f61 | ||
|
|
aec38e367b | ||
|
|
6ad7009509 | ||
|
|
46505ba8a5 | ||
|
|
4011d6e29b | ||
|
|
7ee7b753d6 | ||
|
|
c77f41d08e | ||
|
|
ab7fa150fe | ||
|
|
22fa5d27e3 | ||
|
|
5f05002c20 | ||
|
|
0cd33de2f6 | ||
|
|
e46d19edfe | ||
|
|
18ff3c3c48 | ||
|
|
da1c9a448e | ||
|
|
58e1f71711 | ||
|
|
918899d346 | ||
|
|
7f57e1d9a2 | ||
|
|
df6dc6c8ac | ||
|
|
840bd32ee3 | ||
|
|
da3d056d81 | ||
|
|
b3ea48333c | ||
|
|
f37b39aad2 | ||
|
|
d4c987e48a | ||
|
|
955e38ea0b | ||
|
|
7d87182b1a | ||
|
|
5e80002297 | ||
|
|
1364cd0d6b | ||
|
|
5d21af0e02 | ||
|
|
64afccb36c | ||
|
|
5b0497e14e | ||
|
|
5010bb5665 | ||
|
|
c7789da1ad | ||
|
|
b853ce221d | ||
|
|
3522f81025 | ||
|
|
a22c0c4787 | ||
|
|
4dfc5ead54 | ||
|
|
c667bda427 | ||
|
|
188b129da4 | ||
|
|
6845b51def | ||
|
|
c8ec19e371 | ||
|
|
c9002d2391 | ||
|
|
0ba4cc4d4c | ||
|
|
5baade58fb | ||
|
|
e667fe8a5e |
@@ -8,28 +8,13 @@ FROM mcr.microsoft.com/devcontainers/python:${VARIANT}
|
|||||||
ARG NODE_VERSION="none"
|
ARG NODE_VERSION="none"
|
||||||
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
|
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
|
||||||
|
|
||||||
# install poetry - respects $POETRY_VERSION & $POETRY_HOME
|
|
||||||
|
|
||||||
RUN echo "export PROMPT_COMMAND='history -a'" >> /home/vscode/.bashrc \
|
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
|
RUN npm install -g @go-task/cli
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED=1 \
|
# Install additional OS packages
|
||||||
PYTHONDONTWRITEBYTECODE=1 \
|
|
||||||
PIP_NO_CACHE_DIR=off \
|
|
||||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
|
||||||
PIP_DEFAULT_TIMEOUT=100 \
|
|
||||||
POETRY_HOME="/opt/poetry" \
|
|
||||||
POETRY_VIRTUALENVS_IN_PROJECT=true
|
|
||||||
|
|
||||||
# prepend poetry and venv to path
|
|
||||||
ENV PATH="$POETRY_HOME/bin:$PATH"
|
|
||||||
|
|
||||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
|
||||||
# RUN poetry config virtualenvs.create false
|
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install --no-install-recommends -y \
|
&& apt-get install --no-install-recommends -y \
|
||||||
curl \
|
curl \
|
||||||
@@ -39,5 +24,9 @@ RUN apt-get update \
|
|||||||
libsasl2-dev libldap2-dev libssl-dev \
|
libsasl2-dev libldap2-dev libssl-dev \
|
||||||
gnupg gnupg2 gnupg1
|
gnupg gnupg2 gnupg1
|
||||||
|
|
||||||
# create directory used for Docker Secrets
|
# Install uv
|
||||||
|
RUN pip install uv
|
||||||
|
ENV UV_LINK_MODE=copy
|
||||||
|
|
||||||
|
# Create directory for Docker Secrets
|
||||||
RUN mkdir -p /run/secrets
|
RUN mkdir -p /run/secrets
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
// Use -bullseye variants on local on arm64/Apple Silicon.
|
// Use -bullseye variants on local on arm64/Apple Silicon.
|
||||||
"VARIANT": "3.12-bullseye",
|
"VARIANT": "3.12-bullseye",
|
||||||
// Options
|
// Options
|
||||||
"NODE_VERSION": "16"
|
"NODE_VERSION": "22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mounts": [
|
"mounts": [
|
||||||
@@ -48,12 +48,13 @@
|
|||||||
],
|
],
|
||||||
// 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 /home/vscode/commandhistory && task setup",
|
"onCreateCommand": "sudo chown -R vscode:vscode /workspaces/mealie/frontend/node_modules /home/vscode/commandhistory && task setup --force",
|
||||||
// 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": {
|
||||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
||||||
"dockerDashComposeVersion": "v2"
|
"dockerDashComposeVersion": "v2"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"appPort": 3000
|
||||||
}
|
}
|
||||||
|
|||||||
11
.github/workflows/build-package.yml
vendored
11
.github/workflows/build-package.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
- name: Setup node env 🏗
|
- name: Setup node env 🏗
|
||||||
uses: actions/setup-node@v4.0.0
|
uses: actions/setup-node@v4.0.0
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 22
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Get yarn cache directory path 🛠
|
- name: Get yarn cache directory path 🛠
|
||||||
@@ -70,13 +70,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
|
|
||||||
- name: Install Poetry
|
- name: Install uv
|
||||||
uses: snok/install-poetry@v1
|
run: pip install uv
|
||||||
with:
|
|
||||||
virtualenvs-create: true
|
|
||||||
virtualenvs-in-project: true
|
|
||||||
plugins: |
|
|
||||||
poetry-plugin-export
|
|
||||||
|
|
||||||
- name: Retrieve built frontend
|
- name: Retrieve built frontend
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
|
|||||||
2
.github/workflows/e2e.yml
vendored
2
.github/workflows/e2e.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 22
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
cache-dependency-path: ./tests/e2e/yarn.lock
|
cache-dependency-path: ./tests/e2e/yarn.lock
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
|
|||||||
112
.github/workflows/locale-sync.yml
vendored
Normal file
112
.github/workflows/locale-sync.yml
vendored
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
name: Automatic Locale Sync
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Run every Sunday at 2 AM UTC
|
||||||
|
- cron: "0 2 * * 0"
|
||||||
|
workflow_dispatch:
|
||||||
|
# Allow manual triggering from the GitHub UI
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write # To checkout, commit, and push changes
|
||||||
|
pull-requests: write # To create pull requests
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync-locales:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
run: pip install uv
|
||||||
|
|
||||||
|
- name: Load cached venv
|
||||||
|
id: cached-python-dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .venv
|
||||||
|
key: venv-${{ runner.os }}-${{ hashFiles('**/uv.lock') }}
|
||||||
|
|
||||||
|
- name: Check venv cache
|
||||||
|
id: cache-validate
|
||||||
|
if: steps.cached-python-dependencies.outputs.cache-hit == 'true'
|
||||||
|
run: |
|
||||||
|
echo "import fastapi;print('venv good?')" > test.py && uv run python test.py && echo "cache-hit-success=true" >> $GITHUB_OUTPUT
|
||||||
|
rm test.py
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install libsasl2-dev libldap2-dev libssl-dev
|
||||||
|
uv sync --group dev
|
||||||
|
if: steps.cached-python-dependencies.outputs.cache-hit != 'true'
|
||||||
|
|
||||||
|
- name: Run locale generation
|
||||||
|
run: |
|
||||||
|
cd dev/code-generation
|
||||||
|
uv run python main.py locales
|
||||||
|
env:
|
||||||
|
CROWDIN_API_KEY: ${{ secrets.CROWDIN_API_KEY }}
|
||||||
|
|
||||||
|
- name: Check for changes
|
||||||
|
id: changes
|
||||||
|
run: |
|
||||||
|
if git diff --quiet; then
|
||||||
|
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Commit and create PR
|
||||||
|
if: steps.changes.outputs.has_changes == 'true'
|
||||||
|
run: |
|
||||||
|
# Configure git
|
||||||
|
git config --local user.email "action@github.com"
|
||||||
|
git config --local user.name "GitHub Action"
|
||||||
|
|
||||||
|
# Use the current branch as the base
|
||||||
|
BASE_BRANCH="${{ github.ref_name }}"
|
||||||
|
echo "Using base branch: $BASE_BRANCH"
|
||||||
|
|
||||||
|
# Create a new branch from the base branch
|
||||||
|
BRANCH_NAME="auto-locale-sync-$(date +%Y%m%d-%H%M%S)"
|
||||||
|
git checkout -b "$BRANCH_NAME"
|
||||||
|
|
||||||
|
# Add and commit changes
|
||||||
|
git add .
|
||||||
|
git commit -m "chore: crowdin locale sync"
|
||||||
|
|
||||||
|
# Push the branch
|
||||||
|
git push origin "$BRANCH_NAME"
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Create PR using GitHub CLI with explicit repository
|
||||||
|
gh pr create \
|
||||||
|
--repo "${{ github.repository }}" \
|
||||||
|
--title "chore(l10n): Crowdin locale sync" \
|
||||||
|
--base "$BASE_BRANCH" \
|
||||||
|
--head "$BRANCH_NAME" \
|
||||||
|
--label "l10n" \
|
||||||
|
--body "## Summary
|
||||||
|
|
||||||
|
Automatically generated locale updates from the weekly sync job.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
- Updated frontend locale files
|
||||||
|
- Generated from latest translation sources" \
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: No changes detected
|
||||||
|
if: steps.changes.outputs.has_changes == 'false'
|
||||||
|
run: echo "No locale changes detected, skipping PR creation"
|
||||||
1
.github/workflows/pull-request-lint.yml
vendored
1
.github/workflows/pull-request-lint.yml
vendored
@@ -31,6 +31,7 @@ jobs:
|
|||||||
deps
|
deps
|
||||||
auto
|
auto
|
||||||
l10n
|
l10n
|
||||||
|
config
|
||||||
# Configure that a scope must always be provided.
|
# Configure that a scope must always be provided.
|
||||||
requireScope: false
|
requireScope: false
|
||||||
# If the PR contains one of these newline-delimited labels, the
|
# If the PR contains one of these newline-delimited labels, the
|
||||||
|
|||||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
name: Build Package
|
name: Build Package
|
||||||
uses: ./.github/workflows/build-package.yml
|
uses: ./.github/workflows/build-package.yml
|
||||||
with:
|
with:
|
||||||
tag: release
|
tag: ${{ github.event.release.tag_name }}
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
permissions:
|
permissions:
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:v${{ env.VERSION_NUM }}/' docs/docs/documentation/getting-started/installation/sqlite.md
|
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:v${{ env.VERSION_NUM }}/' docs/docs/documentation/getting-started/installation/sqlite.md
|
||||||
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:v${{ env.VERSION_NUM }}/' docs/docs/documentation/getting-started/installation/postgres.md
|
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:v${{ env.VERSION_NUM }}/' docs/docs/documentation/getting-started/installation/postgres.md
|
||||||
sed -i 's/^version = "[^"]*"/version = "${{ env.VERSION_NUM }}"/' pyproject.toml
|
sed -i 's/^version = "[^"]*"/version = "${{ env.VERSION_NUM }}"/' pyproject.toml
|
||||||
sed -i 's/^\s*"version": "[^"]*"/"version": "${{ env.VERSION_NUM }}"/' frontend/package.json
|
sed -i 's/\("version": "\)[^"]*"/\1${{ env.VERSION_NUM }}"/' frontend/package.json
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v6
|
||||||
|
|||||||
11
.github/workflows/stale.yml
vendored
11
.github/workflows/stale.yml
vendored
@@ -16,12 +16,13 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
stale-issue-label: 'stale'
|
stale-issue-label: 'stale'
|
||||||
exempt-issue-labels: 'pinned,security,early-stages,bug: confirmed,feedback,task'
|
exempt-issue-labels: 'pinned,security,early-stages,bug: confirmed,feedback,task'
|
||||||
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
|
stale-issue-message: 'This issue has been automatically marked as stale because it has been open 90 days with no activity.'
|
||||||
days-before-issue-stale: 30
|
days-before-issue-stale: 90
|
||||||
days-before-issue-close: 5
|
# This stops an issue from ever getting closed automatically.
|
||||||
|
days-before-issue-close: -1
|
||||||
stale-pr-label: 'stale'
|
stale-pr-label: 'stale'
|
||||||
stale-pr-message: 'This PR is stale because it has been open 45 days with no activity.'
|
stale-pr-message: 'This PR has been automatically marked as stale because it has been open 90 days with no activity.'
|
||||||
days-before-pr-stale: 45
|
days-before-pr-stale: 90
|
||||||
# This stops a PR from ever getting closed automatically.
|
# This stops a PR from ever getting closed automatically.
|
||||||
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.
|
||||||
|
|||||||
22
.github/workflows/test-backend.yml
vendored
22
.github/workflows/test-backend.yml
vendored
@@ -49,24 +49,21 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
|
|
||||||
- name: Install Poetry
|
- name: Install uv
|
||||||
uses: snok/install-poetry@v1
|
run: pip install uv
|
||||||
with:
|
|
||||||
virtualenvs-create: true
|
|
||||||
virtualenvs-in-project: true
|
|
||||||
|
|
||||||
- name: Load cached venv
|
- name: Load cached venv
|
||||||
id: cached-poetry-dependencies
|
id: cached-python-dependencies
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: .venv
|
path: .venv
|
||||||
key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
|
key: venv-${{ runner.os }}-${{ hashFiles('**/uv.lock') }}
|
||||||
|
|
||||||
- name: Check venv cache
|
- name: Check venv cache
|
||||||
id: cache-validate
|
id: cache-validate
|
||||||
if: steps.cached-poetry-dependencies.outputs.cache-hit == 'true'
|
if: steps.cached-python-dependencies.outputs.cache-hit == 'true'
|
||||||
run: |
|
run: |
|
||||||
echo "import fastapi;print('venv good?')" > test.py && poetry run python test.py && echo "cache-hit-success=true" >> $GITHUB_OUTPUT
|
echo "import fastapi;print('venv good?')" > test.py && uv run python test.py && echo "cache-hit-success=true" >> $GITHUB_OUTPUT
|
||||||
rm test.py
|
rm test.py
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
@@ -74,13 +71,12 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
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
|
uv sync --group dev --extra pgsql
|
||||||
poetry add "psycopg2-binary==2.9.9"
|
if: steps.cached-python-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 (Ruff)
|
- name: Formatting (Ruff)
|
||||||
run: |
|
run: |
|
||||||
poetry run ruff format . --check
|
uv run ruff format . --check
|
||||||
|
|
||||||
- name: Lint (Ruff)
|
- name: Lint (Ruff)
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
6
.github/workflows/test-frontend.yml
vendored
6
.github/workflows/test-frontend.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
- name: Setup node env 🏗
|
- name: Setup node env 🏗
|
||||||
uses: actions/setup-node@v4.0.0
|
uses: actions/setup-node@v4.0.0
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 22
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Get yarn cache directory path 🛠
|
- name: Get yarn cache directory path 🛠
|
||||||
@@ -34,6 +34,10 @@ jobs:
|
|||||||
run: yarn
|
run: yarn
|
||||||
working-directory: "frontend"
|
working-directory: "frontend"
|
||||||
|
|
||||||
|
- name: Prepare nuxt 🚀
|
||||||
|
run: yarn nuxt prepare
|
||||||
|
working-directory: "frontend"
|
||||||
|
|
||||||
- name: Run linter 👀
|
- name: Run linter 👀
|
||||||
run: yarn lint
|
run: yarn lint
|
||||||
working-directory: "frontend"
|
working-directory: "frontend"
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -10,6 +10,9 @@ docs/site/
|
|||||||
*temp/*
|
*temp/*
|
||||||
.secret
|
.secret
|
||||||
frontend/dist/
|
frontend/dist/
|
||||||
|
frontend/.output/*
|
||||||
|
frontend/.yarn/*
|
||||||
|
frontend/.yarnrc.yml
|
||||||
|
|
||||||
dev/code-generation/generated/*
|
dev/code-generation/generated/*
|
||||||
dev/data/mealie.db-journal
|
dev/data/mealie.db-journal
|
||||||
@@ -164,3 +167,5 @@ dev/code-generation/openapi.json
|
|||||||
|
|
||||||
.run/
|
.run/
|
||||||
.task/*
|
.task/*
|
||||||
|
.dev.env
|
||||||
|
frontend/eslint.config.deprecated.js
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v5.0.0
|
rev: v6.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
exclude: "mkdocs.yml"
|
exclude: "mkdocs.yml"
|
||||||
@@ -12,7 +12,7 @@ repos:
|
|||||||
exclude: ^tests/data/
|
exclude: ^tests/data/
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.11.4
|
rev: v0.14.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
|
|||||||
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@@ -18,6 +18,7 @@
|
|||||||
"source.organizeImports": "never"
|
"source.organizeImports": "never"
|
||||||
},
|
},
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
|
"eslint.useFlatConfig": true,
|
||||||
"eslint.workingDirectories": [
|
"eslint.workingDirectories": [
|
||||||
"./frontend"
|
"./frontend"
|
||||||
],
|
],
|
||||||
@@ -54,12 +55,15 @@
|
|||||||
"explorer.fileNesting.enabled": true,
|
"explorer.fileNesting.enabled": true,
|
||||||
"explorer.fileNesting.patterns": {
|
"explorer.fileNesting.patterns": {
|
||||||
"package.json": "package-lock.json, yarn.lock, .eslintrc.js, tsconfig.json, .prettierrc, .editorconfig",
|
"package.json": "package-lock.json, yarn.lock, .eslintrc.js, tsconfig.json, .prettierrc, .editorconfig",
|
||||||
"pyproject.toml": "poetry.lock, alembic.ini, .pylintrc",
|
"pyproject.toml": "uv.lock, alembic.ini, .pylintrc",
|
||||||
"netlify.toml": "runtime.txt",
|
"netlify.toml": "runtime.txt",
|
||||||
"README.md": "LICENSE, SECURITY.md"
|
"README.md": "LICENSE, SECURITY.md"
|
||||||
},
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.formatOnSave": true
|
||||||
|
},
|
||||||
"[vue]": {
|
"[vue]": {
|
||||||
"editor.formatOnSave": false
|
"editor.formatOnSave": true
|
||||||
},
|
},
|
||||||
"[python]": {
|
"[python]": {
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
|
|||||||
51
Taskfile.yml
51
Taskfile.yml
@@ -28,7 +28,7 @@ tasks:
|
|||||||
docs:gen:
|
docs:gen:
|
||||||
desc: runs the API documentation generator
|
desc: runs the API documentation generator
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run python dev/code-generation/gen_docs_api.py
|
- uv run python dev/code-generation/gen_docs_api.py
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
desc: runs the documentation server
|
desc: runs the documentation server
|
||||||
@@ -36,7 +36,7 @@ tasks:
|
|||||||
deps:
|
deps:
|
||||||
- docs:gen
|
- docs:gen
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run python -m mkdocs serve
|
- uv run python -m mkdocs serve
|
||||||
|
|
||||||
setup:ui:
|
setup:ui:
|
||||||
desc: setup frontend dependencies
|
desc: setup frontend dependencies
|
||||||
@@ -54,10 +54,10 @@ tasks:
|
|||||||
desc: setup python dependencies
|
desc: setup python dependencies
|
||||||
run: once
|
run: once
|
||||||
cmds:
|
cmds:
|
||||||
- poetry install --with main,dev,postgres
|
- uv sync --extra pgsql --group dev
|
||||||
- poetry run pre-commit install
|
- uv run pre-commit install
|
||||||
sources:
|
sources:
|
||||||
- poetry.lock
|
- uv.lock
|
||||||
- pyproject.toml
|
- pyproject.toml
|
||||||
- .pre-commit-config.yaml
|
- .pre-commit-config.yaml
|
||||||
|
|
||||||
@@ -70,7 +70,8 @@ tasks:
|
|||||||
dev:generate:
|
dev:generate:
|
||||||
desc: run code generators
|
desc: run code generators
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run python dev/code-generation/main.py
|
- uv run python dev/code-generation/main.py {{ .CLI_ARGS }}
|
||||||
|
- task: docs:gen
|
||||||
- task: py:format
|
- task: py:format
|
||||||
|
|
||||||
dev:services:
|
dev:services:
|
||||||
@@ -87,28 +88,30 @@ tasks:
|
|||||||
- rm -r ./dev/data/recipes/
|
- rm -r ./dev/data/recipes/
|
||||||
- rm -r ./dev/data/users/
|
- rm -r ./dev/data/users/
|
||||||
- rm -f ./dev/data/mealie*.db
|
- rm -f ./dev/data/mealie*.db
|
||||||
|
- rm -f ./dev/data/mealie*.db-shm
|
||||||
|
- rm -f ./dev/data/mealie*.db-wal
|
||||||
- rm -f ./dev/data/mealie.log
|
- rm -f ./dev/data/mealie.log
|
||||||
- rm -f ./dev/data/.secret
|
- rm -f ./dev/data/.secret
|
||||||
|
|
||||||
py:mypy:
|
py:mypy:
|
||||||
desc: runs python type checking
|
desc: runs python type checking
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run mypy mealie
|
- uv run mypy mealie
|
||||||
|
|
||||||
py:test:
|
py:test:
|
||||||
desc: runs python tests (support args after '--')
|
desc: runs python tests (support args after '--')
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run pytest {{ .CLI_ARGS }}
|
- uv run pytest {{ .CLI_ARGS }}
|
||||||
|
|
||||||
py:format:
|
py:format:
|
||||||
desc: runs python code formatter
|
desc: runs python code formatter
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run ruff format .
|
- uv run ruff format .
|
||||||
|
|
||||||
py:lint:
|
py:lint:
|
||||||
desc: runs python linter
|
desc: runs python linter
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run ruff check mealie
|
- uv run ruff check mealie
|
||||||
|
|
||||||
py:check:
|
py:check:
|
||||||
desc: runs all linters, type checkers, and formatters
|
desc: runs all linters, type checkers, and formatters
|
||||||
@@ -121,10 +124,10 @@ tasks:
|
|||||||
py:coverage:
|
py:coverage:
|
||||||
desc: runs python coverage and generates html report
|
desc: runs python coverage and generates html report
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run pytest
|
- uv run pytest
|
||||||
- poetry run coverage report -m
|
- uv run coverage report -m
|
||||||
- poetry run coveragepy-lcov
|
- uv run coveragepy-lcov
|
||||||
- poetry run coverage html
|
- uv run coverage html
|
||||||
- open htmlcov/index.html
|
- open htmlcov/index.html
|
||||||
|
|
||||||
py:package:copy-frontend:
|
py:package:copy-frontend:
|
||||||
@@ -144,17 +147,17 @@ tasks:
|
|||||||
desc: Generate requirements file to pin all packages, effectively a "pip freeze" before installation begins
|
desc: Generate requirements file to pin all packages, effectively a "pip freeze" before installation begins
|
||||||
internal: true
|
internal: true
|
||||||
cmds:
|
cmds:
|
||||||
- poetry export -n --only=main --extras=pgsql --output=dist/requirements.txt
|
- uv export --no-editable --no-emit-project --extra pgsql --format requirements-txt --output-file dist/requirements.txt
|
||||||
# Include mealie in the requirements, hashing the package that was just built to ensure it's the one installed
|
# Include mealie in the requirements, hashing the package that was just built to ensure it's the one installed
|
||||||
- echo "mealie[pgsql]=={{.MEALIE_VERSION}} \\" >> dist/requirements.txt
|
- echo "mealie[pgsql]=={{.MEALIE_VERSION}} \\" >> dist/requirements.txt
|
||||||
- poetry run pip hash dist/mealie-{{.MEALIE_VERSION}}-py3-none-any.whl | tail -n1 | tr -d '\n' >> dist/requirements.txt
|
- pip hash dist/mealie-{{.MEALIE_VERSION}}-py3-none-any.whl | tail -n1 | tr -d '\n' >> dist/requirements.txt
|
||||||
- echo " \\" >> dist/requirements.txt
|
- echo " \\" >> dist/requirements.txt
|
||||||
- poetry run pip hash dist/mealie-{{.MEALIE_VERSION}}.tar.gz | tail -n1 >> dist/requirements.txt
|
- pip hash dist/mealie-{{.MEALIE_VERSION}}.tar.gz | tail -n1 >> dist/requirements.txt
|
||||||
vars:
|
vars:
|
||||||
MEALIE_VERSION:
|
MEALIE_VERSION:
|
||||||
sh: poetry version --short
|
sh: python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])"
|
||||||
sources:
|
sources:
|
||||||
- poetry.lock
|
- uv.lock
|
||||||
- pyproject.toml
|
- pyproject.toml
|
||||||
- dist/mealie-*.whl
|
- dist/mealie-*.whl
|
||||||
- dist/mealie-*.tar.gz
|
- dist/mealie-*.tar.gz
|
||||||
@@ -181,13 +184,13 @@ tasks:
|
|||||||
deps:
|
deps:
|
||||||
- py:package:deps
|
- py:package:deps
|
||||||
cmds:
|
cmds:
|
||||||
- poetry build -n --output=dist
|
- uv build --out-dir dist
|
||||||
- task: py:package:generate-requirements
|
- task: py:package:generate-requirements
|
||||||
|
|
||||||
py:
|
py:
|
||||||
desc: runs the backend server
|
desc: runs the backend server
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run python mealie/app.py
|
- uv run python mealie/app.py
|
||||||
|
|
||||||
py:postgres:
|
py:postgres:
|
||||||
desc: runs the backend server configured for containerized postgres
|
desc: runs the backend server configured for containerized postgres
|
||||||
@@ -199,12 +202,12 @@ tasks:
|
|||||||
POSTGRES_PORT: 5432
|
POSTGRES_PORT: 5432
|
||||||
POSTGRES_DB: mealie
|
POSTGRES_DB: mealie
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run python mealie/app.py
|
- uv run python mealie/app.py
|
||||||
|
|
||||||
py:migrate:
|
py:migrate:
|
||||||
desc: generates a new database migration file e.g. task py:migrate -- "add new column"
|
desc: generates a new database migration file e.g. task py:migrate -- "add new column"
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run alembic --config mealie/alembic/alembic.ini revision --autogenerate -m "{{ .CLI_ARGS }}"
|
- uv run alembic --config mealie/alembic/alembic.ini revision --autogenerate -m "{{ .CLI_ARGS }}"
|
||||||
- task: py:format
|
- task: py:format
|
||||||
|
|
||||||
ui:build:
|
ui:build:
|
||||||
@@ -243,7 +246,7 @@ tasks:
|
|||||||
desc: runs the frontend server
|
desc: runs the frontend server
|
||||||
dir: frontend
|
dir: frontend
|
||||||
cmds:
|
cmds:
|
||||||
- yarn run dev
|
- yarn run dev --no-fork
|
||||||
|
|
||||||
docker:build-from-package:
|
docker:build-from-package:
|
||||||
desc: Builds the Docker image from the existing Python package in dist/
|
desc: Builds the Docker image from the existing Python package in dist/
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ conventional_commits = true
|
|||||||
filter_unconventional = true
|
filter_unconventional = true
|
||||||
# regex for preprocessing the commit messages
|
# regex for preprocessing the commit messages
|
||||||
commit_preprocessors = [
|
commit_preprocessors = [
|
||||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/hay-kot/mealie/issues/${2}))"},
|
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/mealie-recipes/mealie/issues/${2}))"},
|
||||||
]
|
]
|
||||||
# regex for parsing and grouping commits
|
# regex for parsing and grouping commits
|
||||||
commit_parsers = [
|
commit_parsers = [
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import json
|
import json
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from freezegun import freeze_time
|
|
||||||
|
|
||||||
from mealie.app import app
|
from mealie.app import app
|
||||||
from mealie.core.config import determine_data_dir
|
from mealie.core.config import determine_data_dir
|
||||||
@@ -37,14 +38,43 @@ HTML_TEMPLATE = """<!-- Custom HTML site displayed as the Home chapter -->
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
HTML_PATH = DATA_DIR.parent.parent.joinpath("docs/docs/overrides/api.html")
|
HTML_PATH = DATA_DIR.parent.parent.joinpath("docs/docs/overrides/api.html")
|
||||||
|
CONSTANT_DT = datetime(2025, 10, 24, 15, 53, 0, 0, tzinfo=UTC)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_timestamps(s: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
field_format = s.get("format")
|
||||||
|
is_timestamp = field_format in ["date-time", "date", "time"]
|
||||||
|
has_default = s.get("default")
|
||||||
|
|
||||||
|
if not is_timestamp:
|
||||||
|
for k, v in s.items():
|
||||||
|
if isinstance(v, dict):
|
||||||
|
s[k] = normalize_timestamps(v)
|
||||||
|
elif isinstance(v, list):
|
||||||
|
s[k] = [normalize_timestamps(i) if isinstance(i, dict) else i for i in v]
|
||||||
|
|
||||||
|
return s
|
||||||
|
elif not has_default:
|
||||||
|
return s
|
||||||
|
|
||||||
|
if field_format == "date-time":
|
||||||
|
s["default"] = CONSTANT_DT.isoformat()
|
||||||
|
elif field_format == "date":
|
||||||
|
s["default"] = CONSTANT_DT.date().isoformat()
|
||||||
|
elif field_format == "time":
|
||||||
|
s["default"] = CONSTANT_DT.time().isoformat()
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
def generate_api_docs(my_app: FastAPI):
|
def generate_api_docs(my_app: FastAPI):
|
||||||
|
openapi_schema = my_app.openapi()
|
||||||
|
openapi_schema = normalize_timestamps(openapi_schema)
|
||||||
|
|
||||||
with open(HTML_PATH, "w") as fd:
|
with open(HTML_PATH, "w") as fd:
|
||||||
text = HTML_TEMPLATE.replace("MY_SPECIFIC_TEXT", json.dumps(my_app.openapi()))
|
text = HTML_TEMPLATE.replace("MY_SPECIFIC_TEXT", json.dumps(openapi_schema))
|
||||||
fd.write(text)
|
fd.write(text)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
with freeze_time("2024-01-20T17:00:55Z"):
|
generate_api_docs(app)
|
||||||
generate_api_docs(app)
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import subprocess
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -105,12 +106,16 @@ def main():
|
|||||||
# Flatten list of lists
|
# Flatten list of lists
|
||||||
all_children = [item for sublist in all_children for item in sublist]
|
all_children = [item for sublist in all_children for item in sublist]
|
||||||
|
|
||||||
|
out_path = GENERATED / "__init__.py"
|
||||||
render_python_template(
|
render_python_template(
|
||||||
TEMPLATE,
|
TEMPLATE,
|
||||||
GENERATED / "__init__.py",
|
out_path,
|
||||||
{"children": all_children},
|
{"children": all_children},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
subprocess.run(["uv", "run", "ruff", "check", str(out_path), "--fix"])
|
||||||
|
subprocess.run(["uv", "run", "ruff", "format", str(out_path)])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
from utils import PROJECT_DIR, log, render_python_template
|
from utils import PROJECT_DIR, log, render_python_template
|
||||||
@@ -84,16 +85,23 @@ def find_modules(root: pathlib.Path) -> list[Modules]:
|
|||||||
return modules
|
return modules
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
modules = find_modules(SCHEMA_PATH)
|
modules = find_modules(SCHEMA_PATH)
|
||||||
|
|
||||||
|
template_paths: list[pathlib.Path] = []
|
||||||
for module in modules:
|
for module in modules:
|
||||||
log.debug(f"Module: {module.directory.name}")
|
log.debug(f"Module: {module.directory.name}")
|
||||||
for file in module.files:
|
for file in module.files:
|
||||||
log.debug(f" File: {file.import_path}")
|
log.debug(f" File: {file.import_path}")
|
||||||
log.debug(f" Classes: [{', '.join(file.classes)}]")
|
log.debug(f" Classes: [{', '.join(file.classes)}]")
|
||||||
|
|
||||||
render_python_template(template, module.directory / "__init__.py", {"module": module})
|
template_path = module.directory / "__init__.py"
|
||||||
|
template_paths.append(template_path)
|
||||||
|
render_python_template(template, template_path, {"module": module})
|
||||||
|
|
||||||
|
path_args = (str(p) for p in template_paths)
|
||||||
|
subprocess.run(["uv", "run", "ruff", "check", *path_args, "--fix"])
|
||||||
|
subprocess.run(["uv", "run", "ruff", "format", *path_args])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -13,7 +14,7 @@ from mealie.schema._mealie import MealieModel
|
|||||||
|
|
||||||
BASE = pathlib.Path(__file__).parent.parent.parent
|
BASE = pathlib.Path(__file__).parent.parent.parent
|
||||||
|
|
||||||
API_KEY = dotenv.get_key(BASE / ".env", "CROWDIN_API_KEY")
|
API_KEY = dotenv.get_key(BASE / ".env", "CROWDIN_API_KEY") or os.environ.get("CROWDIN_API_KEY", "")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -23,19 +24,22 @@ class LocaleData:
|
|||||||
|
|
||||||
|
|
||||||
LOCALE_DATA: dict[str, LocaleData] = {
|
LOCALE_DATA: dict[str, LocaleData] = {
|
||||||
"en-US": LocaleData(name="American English"),
|
|
||||||
"en-GB": LocaleData(name="British English"),
|
|
||||||
"af-ZA": LocaleData(name="Afrikaans (Afrikaans)"),
|
"af-ZA": LocaleData(name="Afrikaans (Afrikaans)"),
|
||||||
"ar-SA": LocaleData(name="العربية (Arabic)", dir="rtl"),
|
"ar-SA": LocaleData(name="العربية (Arabic)", dir="rtl"),
|
||||||
|
"bg-BG": LocaleData(name="Български (Bulgarian)"),
|
||||||
"ca-ES": LocaleData(name="Català (Catalan)"),
|
"ca-ES": LocaleData(name="Català (Catalan)"),
|
||||||
"cs-CZ": LocaleData(name="Čeština (Czech)"),
|
"cs-CZ": LocaleData(name="Čeština (Czech)"),
|
||||||
"da-DK": LocaleData(name="Dansk (Danish)"),
|
"da-DK": LocaleData(name="Dansk (Danish)"),
|
||||||
"de-DE": LocaleData(name="Deutsch (German)"),
|
"de-DE": LocaleData(name="Deutsch (German)"),
|
||||||
"el-GR": LocaleData(name="Ελληνικά (Greek)"),
|
"el-GR": LocaleData(name="Ελληνικά (Greek)"),
|
||||||
|
"en-GB": LocaleData(name="British English"),
|
||||||
|
"en-US": LocaleData(name="American English"),
|
||||||
"es-ES": LocaleData(name="Español (Spanish)"),
|
"es-ES": LocaleData(name="Español (Spanish)"),
|
||||||
|
"et-EE": LocaleData(name="Eesti (Estonian)"),
|
||||||
"fi-FI": LocaleData(name="Suomi (Finnish)"),
|
"fi-FI": LocaleData(name="Suomi (Finnish)"),
|
||||||
"fr-FR": LocaleData(name="Français (French)"),
|
|
||||||
"fr-BE": LocaleData(name="Belge (Belgian)"),
|
"fr-BE": LocaleData(name="Belge (Belgian)"),
|
||||||
|
"fr-CA": LocaleData(name="Français canadien (Canadian French)"),
|
||||||
|
"fr-FR": LocaleData(name="Français (French)"),
|
||||||
"gl-ES": LocaleData(name="Galego (Galician)"),
|
"gl-ES": LocaleData(name="Galego (Galician)"),
|
||||||
"he-IL": LocaleData(name="עברית (Hebrew)", dir="rtl"),
|
"he-IL": LocaleData(name="עברית (Hebrew)", dir="rtl"),
|
||||||
"hr-HR": LocaleData(name="Hrvatski (Croatian)"),
|
"hr-HR": LocaleData(name="Hrvatski (Croatian)"),
|
||||||
@@ -53,6 +57,7 @@ LOCALE_DATA: dict[str, LocaleData] = {
|
|||||||
"pt-PT": LocaleData(name="Português (Portuguese)"),
|
"pt-PT": LocaleData(name="Português (Portuguese)"),
|
||||||
"ro-RO": LocaleData(name="Română (Romanian)"),
|
"ro-RO": LocaleData(name="Română (Romanian)"),
|
||||||
"ru-RU": LocaleData(name="Pусский (Russian)"),
|
"ru-RU": LocaleData(name="Pусский (Russian)"),
|
||||||
|
"sk-SK": LocaleData(name="Slovenčina (Slovak)"),
|
||||||
"sl-SI": LocaleData(name="Slovenščina (Slovenian)"),
|
"sl-SI": LocaleData(name="Slovenščina (Slovenian)"),
|
||||||
"sr-SP": LocaleData(name="српски (Serbian)"),
|
"sr-SP": LocaleData(name="српски (Serbian)"),
|
||||||
"sv-SE": LocaleData(name="Svenska (Swedish)"),
|
"sv-SE": LocaleData(name="Svenska (Swedish)"),
|
||||||
@@ -71,7 +76,7 @@ export const LOCALES = [{% for locale in locales %}
|
|||||||
progress: {{ locale.progress }},
|
progress: {{ locale.progress }},
|
||||||
dir: "{{ locale.dir }}",
|
dir: "{{ locale.dir }}",
|
||||||
},{% endfor %}
|
},{% endfor %}
|
||||||
]
|
];
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -93,8 +98,8 @@ class CrowdinApi:
|
|||||||
project_id = "451976"
|
project_id = "451976"
|
||||||
api_key = API_KEY
|
api_key = API_KEY
|
||||||
|
|
||||||
def __init__(self, api_key: str):
|
def __init__(self, api_key: str | None):
|
||||||
api_key = api_key
|
self.api_key = api_key or API_KEY
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def headers(self) -> dict:
|
def headers(self) -> dict:
|
||||||
@@ -156,29 +161,51 @@ 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.ts"
|
||||||
|
i18n_config = PROJECT_DIR / "frontend" / "i18n.config.ts"
|
||||||
reg_valid = PROJECT_DIR / "mealie" / "schema" / "_mealie" / "validators.py"
|
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
|
||||||
for the nuxt.config.js file and automatically injects it into the nuxt.config.js file. Note that
|
for the nuxt.config.ts file and automatically injects it into the nuxt.config.ts file. Note that
|
||||||
the code generation ID is hardcoded into the script and required in the nuxt config.
|
the code generation ID is hardcoded into the script and required in the nuxt config.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def inject_nuxt_values():
|
def inject_nuxt_values():
|
||||||
all_date_locales = [
|
datetime_files = list(datetime_dir.glob("*.json"))
|
||||||
f'"{match.stem}": require("./lang/dateTimeFormats/{match.name}"),' for match in datetime_dir.glob("*.json")
|
datetime_files.sort()
|
||||||
]
|
|
||||||
|
datetime_imports = []
|
||||||
|
datetime_object_entries = []
|
||||||
|
|
||||||
|
for match in datetime_files:
|
||||||
|
# Convert locale name to camelCase variable name (e.g., "en-US" -> "enUS")
|
||||||
|
var_name = match.stem.replace("-", "")
|
||||||
|
|
||||||
|
# Generate import statement
|
||||||
|
import_line = f'import * as {var_name} from "./lang/dateTimeFormats/{match.name}";'
|
||||||
|
datetime_imports.append(import_line)
|
||||||
|
|
||||||
|
# Generate object entry
|
||||||
|
object_entry = f' "{match.stem}": {var_name},'
|
||||||
|
datetime_object_entries.append(object_entry)
|
||||||
|
|
||||||
|
all_date_locales = datetime_imports + ["", "const datetimeFormats = {"] + datetime_object_entries + ["};"]
|
||||||
|
|
||||||
all_langs = []
|
all_langs = []
|
||||||
for match in locales_dir.glob("*.json"):
|
for match in locales_dir.glob("*.json"):
|
||||||
lang_string = f'{{ code: "{match.stem}", file: "{match.name}" }},'
|
match_data = LOCALE_DATA.get(match.stem)
|
||||||
|
match_dir = match_data.dir if match_data else "ltr"
|
||||||
|
|
||||||
|
lang_string = f'{{ code: "{match.stem}", file: "{match.name.replace(".json", ".ts")}", dir: "{match_dir}" }},'
|
||||||
all_langs.append(lang_string)
|
all_langs.append(lang_string)
|
||||||
|
|
||||||
|
all_langs.sort()
|
||||||
|
|
||||||
log.debug(f"injecting locales into nuxt config -> {nuxt_config}")
|
log.debug(f"injecting locales into nuxt config -> {nuxt_config}")
|
||||||
inject_inline(nuxt_config, CodeKeys.nuxt_local_messages, all_langs)
|
inject_inline(nuxt_config, CodeKeys.nuxt_local_messages, all_langs)
|
||||||
inject_inline(nuxt_config, CodeKeys.nuxt_local_dates, all_date_locales)
|
inject_inline(i18n_config, CodeKeys.nuxt_local_dates, all_date_locales)
|
||||||
|
|
||||||
|
|
||||||
def inject_registration_validation_values():
|
def inject_registration_validation_values():
|
||||||
@@ -195,7 +222,7 @@ def inject_registration_validation_values():
|
|||||||
|
|
||||||
|
|
||||||
def generate_locales_ts_file():
|
def generate_locales_ts_file():
|
||||||
api = CrowdinApi("")
|
api = CrowdinApi(None)
|
||||||
models = api.get_languages()
|
models = api.get_languages()
|
||||||
tmpl = Template(LOCALE_TEMPLATE)
|
tmpl = Template(LOCALE_TEMPLATE)
|
||||||
rendered = tmpl.render(locales=models)
|
rendered = tmpl.render(locales=models)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
@@ -8,8 +9,8 @@ from utils import log
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
template = """// This Code is auto generated by gen_ts_types.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 type {{ name }} from "@/components/global/{{ name }}.vue";
|
||||||
{% endfor %}{% for name in layout %}import {{ name }} from "@/components/layout/{{ name }}.vue";
|
{% endfor %}{% for name in layout %}import type {{ name }} from "@/components/layout/{{ name }}.vue";
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
declare module "vue" {
|
declare module "vue" {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
@@ -189,6 +190,7 @@ def generate_typescript_types() -> None: # noqa: C901
|
|||||||
skipped_dirs: list[Path] = []
|
skipped_dirs: list[Path] = []
|
||||||
failed_modules: list[Path] = []
|
failed_modules: list[Path] = []
|
||||||
|
|
||||||
|
out_paths: list[Path] = []
|
||||||
for module in schema_path.iterdir():
|
for module in schema_path.iterdir():
|
||||||
if module.is_dir() and module.stem in ignore_dirs:
|
if module.is_dir() and module.stem in ignore_dirs:
|
||||||
skipped_dirs.append(module)
|
skipped_dirs.append(module)
|
||||||
@@ -205,10 +207,18 @@ def generate_typescript_types() -> None: # noqa: C901
|
|||||||
path_as_module = path_to_module(module)
|
path_as_module = path_to_module(module)
|
||||||
generate_typescript_defs(path_as_module, str(out_path), exclude=("MealieModel")) # type: ignore
|
generate_typescript_defs(path_as_module, str(out_path), exclude=("MealieModel")) # type: ignore
|
||||||
clean_output_file(out_path)
|
clean_output_file(out_path)
|
||||||
|
out_paths.append(out_path)
|
||||||
except Exception:
|
except Exception:
|
||||||
failed_modules.append(module)
|
failed_modules.append(module)
|
||||||
log.exception(f"Module Error: {module}")
|
log.exception(f"Module Error: {module}")
|
||||||
|
|
||||||
|
# Run ESLint --fix on the files to clean up any formatting issues
|
||||||
|
subprocess.run(
|
||||||
|
["yarn", "lint", "--fix", *(str(path) for path in out_paths)],
|
||||||
|
check=True,
|
||||||
|
cwd=PROJECT_DIR / "frontend",
|
||||||
|
)
|
||||||
|
|
||||||
log.debug("\n📁 Skipped Directories:")
|
log.debug("\n📁 Skipped Directories:")
|
||||||
for skipped_dir in skipped_dirs:
|
for skipped_dir in skipped_dirs:
|
||||||
log.debug(f" 📁 {skipped_dir.name}")
|
log.debug(f" 📁 {skipped_dir.name}")
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import argparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import gen_py_pytest_data_paths
|
import gen_py_pytest_data_paths
|
||||||
@@ -11,15 +12,39 @@ CWD = Path(__file__).parent
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
items = [
|
parser = argparse.ArgumentParser(description="Run code generators")
|
||||||
(gen_py_schema_exports.main, "schema exports"),
|
parser.add_argument(
|
||||||
(gen_ts_types.main, "frontend types"),
|
"generators",
|
||||||
(gen_ts_locales.main, "locales"),
|
nargs="*",
|
||||||
(gen_py_pytest_data_paths.main, "test data paths"),
|
help="Specific generators to run (schema, types, locales, data-paths, routes). If none specified, all will run.", # noqa: E501 - long line
|
||||||
(gen_py_pytest_routes.main, "pytest routes"),
|
)
|
||||||
]
|
args = parser.parse_args()
|
||||||
|
|
||||||
for func, name in items:
|
# Define all available generators
|
||||||
|
all_generators = {
|
||||||
|
"schema": (gen_py_schema_exports.main, "schema exports"),
|
||||||
|
"types": (gen_ts_types.main, "frontend types"),
|
||||||
|
"locales": (gen_ts_locales.main, "locales"),
|
||||||
|
"data-paths": (gen_py_pytest_data_paths.main, "test data paths"),
|
||||||
|
"routes": (gen_py_pytest_routes.main, "pytest routes"),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine which generators to run
|
||||||
|
if args.generators:
|
||||||
|
# Validate requested generators
|
||||||
|
invalid_generators = [g for g in args.generators if g not in all_generators]
|
||||||
|
if invalid_generators:
|
||||||
|
log.error(f"Invalid generator(s): {', '.join(invalid_generators)}")
|
||||||
|
log.info(f"Available generators: {', '.join(all_generators.keys())}")
|
||||||
|
return
|
||||||
|
|
||||||
|
generators_to_run = [(all_generators[g][0], all_generators[g][1]) for g in args.generators]
|
||||||
|
else:
|
||||||
|
# Run all generators (default behavior)
|
||||||
|
generators_to_run = list(all_generators.values())
|
||||||
|
|
||||||
|
# Run the selected generators
|
||||||
|
for func, name in generators_to_run:
|
||||||
log.info(f"Generating {name}...")
|
log.info(f"Generating {name}...")
|
||||||
func()
|
func()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -24,21 +22,16 @@ def render_python_template(template_file: Path | str, dest: Path, data: dict):
|
|||||||
|
|
||||||
dest.write_text(text)
|
dest.write_text(text)
|
||||||
|
|
||||||
# lint/format file with Ruff
|
|
||||||
log.info(f"Formatting {dest}")
|
|
||||||
subprocess.run(["poetry", "run", "ruff", "check", str(dest), "--fix"])
|
|
||||||
subprocess.run(["poetry", "run", "ruff", "format", str(dest)])
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CodeSlicer:
|
class CodeSlicer:
|
||||||
start: int
|
start: int
|
||||||
end: int
|
end: int
|
||||||
|
|
||||||
indentation: str
|
indentation: str | None
|
||||||
text: list[str]
|
text: list[str]
|
||||||
|
|
||||||
_next_line = None
|
_next_line: int | None = None
|
||||||
|
|
||||||
def purge_lines(self) -> None:
|
def purge_lines(self) -> None:
|
||||||
start = self.start + 1
|
start = self.start + 1
|
||||||
@@ -47,15 +40,24 @@ class CodeSlicer:
|
|||||||
|
|
||||||
def push_line(self, string: str) -> None:
|
def push_line(self, string: str) -> None:
|
||||||
self._next_line = self._next_line or self.start + 1
|
self._next_line = self._next_line or self.start + 1
|
||||||
self.text.insert(self._next_line, self.indentation + string + "\n")
|
self.text.insert(self._next_line, (self.indentation or "") + string + "\n")
|
||||||
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) -> str:
|
||||||
return re.sub(rf"{comment_char}.*", "", line).removesuffix("\n")
|
# Extract everything before the comment
|
||||||
|
if "//" in line:
|
||||||
|
indentation = line.split("//")[0]
|
||||||
|
elif "#" in line:
|
||||||
|
indentation = line.split("#")[0]
|
||||||
|
else:
|
||||||
|
indentation = line
|
||||||
|
|
||||||
|
# Keep only the whitespace, remove any non-whitespace characters
|
||||||
|
return "".join(c for c in indentation if c.isspace())
|
||||||
|
|
||||||
|
|
||||||
def find_start_end(file_text: list[str], gen_id: str) -> tuple[int, int, str]:
|
def find_start_end(file_text: list[str], gen_id: str) -> tuple[int, int, str | None]:
|
||||||
start = None
|
start = None
|
||||||
end = None
|
end = None
|
||||||
indentation = None
|
indentation = None
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
# {{ recipe.name }}
|
|
||||||
{{ recipe.description }}
|
|
||||||
|
|
||||||
## Ingredients
|
|
||||||
{% for ingredient in recipe.recipeIngredient %}
|
|
||||||
- [ ] {{ ingredient }} {% endfor %}
|
|
||||||
|
|
||||||
## Instructions
|
|
||||||
{% for step in recipe.recipeInstructions %}
|
|
||||||
- [ ] {{ step.text }} {% endfor %}
|
|
||||||
|
|
||||||
{% for note in recipe.notes %}
|
|
||||||
**{{ note.title }}:** {{ note.text }}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Tags: {{ recipe.tags }}
|
|
||||||
Categories: {{ recipe.categories }}
|
|
||||||
Original URL: {{ recipe.orgURL }}
|
|
||||||
@@ -44,7 +44,6 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic
|
|||||||
"note": "1 cup unsalted butter, cut into cubes",
|
"note": "1 cup unsalted butter, cut into cubes",
|
||||||
"unit": None,
|
"unit": None,
|
||||||
"food": None,
|
"food": None,
|
||||||
"disableAmount": True,
|
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"originalText": None,
|
"originalText": None,
|
||||||
"referenceId": "ea3b6702-9532-4fbc-a40b-f99917831c26",
|
"referenceId": "ea3b6702-9532-4fbc-a40b-f99917831c26",
|
||||||
@@ -54,7 +53,6 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic
|
|||||||
"note": "1 cup light brown sugar",
|
"note": "1 cup light brown sugar",
|
||||||
"unit": None,
|
"unit": None,
|
||||||
"food": None,
|
"food": None,
|
||||||
"disableAmount": True,
|
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"originalText": None,
|
"originalText": None,
|
||||||
"referenceId": "c5bbfefb-1e23-4ffd-af88-c0363a0fae82",
|
"referenceId": "c5bbfefb-1e23-4ffd-af88-c0363a0fae82",
|
||||||
@@ -64,7 +62,6 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic
|
|||||||
"note": "1/2 cup granulated white sugar",
|
"note": "1/2 cup granulated white sugar",
|
||||||
"unit": None,
|
"unit": None,
|
||||||
"food": None,
|
"food": None,
|
||||||
"disableAmount": True,
|
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"originalText": None,
|
"originalText": None,
|
||||||
"referenceId": "034f481b-c426-4a17-b983-5aea9be4974b",
|
"referenceId": "034f481b-c426-4a17-b983-5aea9be4974b",
|
||||||
@@ -74,7 +71,6 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic
|
|||||||
"note": "2 large eggs",
|
"note": "2 large eggs",
|
||||||
"unit": None,
|
"unit": None,
|
||||||
"food": None,
|
"food": None,
|
||||||
"disableAmount": True,
|
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"originalText": None,
|
"originalText": None,
|
||||||
"referenceId": "37c1f796-3bdb-4856-859f-dbec90bc27e4",
|
"referenceId": "37c1f796-3bdb-4856-859f-dbec90bc27e4",
|
||||||
@@ -84,7 +80,6 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic
|
|||||||
"note": "2 tsp vanilla extract",
|
"note": "2 tsp vanilla extract",
|
||||||
"unit": None,
|
"unit": None,
|
||||||
"food": None,
|
"food": None,
|
||||||
"disableAmount": True,
|
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"originalText": None,
|
"originalText": None,
|
||||||
"referenceId": "85561ace-f249-401d-834c-e600a2f6280e",
|
"referenceId": "85561ace-f249-401d-834c-e600a2f6280e",
|
||||||
@@ -94,7 +89,6 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic
|
|||||||
"note": "1/2 cup creamy peanut butter",
|
"note": "1/2 cup creamy peanut butter",
|
||||||
"unit": None,
|
"unit": None,
|
||||||
"food": None,
|
"food": None,
|
||||||
"disableAmount": True,
|
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"originalText": None,
|
"originalText": None,
|
||||||
"referenceId": "ac91bda0-e8a8-491a-976a-ae4e72418cfd",
|
"referenceId": "ac91bda0-e8a8-491a-976a-ae4e72418cfd",
|
||||||
@@ -104,7 +98,6 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic
|
|||||||
"note": "1 tsp cornstarch",
|
"note": "1 tsp cornstarch",
|
||||||
"unit": None,
|
"unit": None,
|
||||||
"food": None,
|
"food": None,
|
||||||
"disableAmount": True,
|
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"originalText": None,
|
"originalText": None,
|
||||||
"referenceId": "4d1256b3-115e-4475-83cd-464fbc304cb0",
|
"referenceId": "4d1256b3-115e-4475-83cd-464fbc304cb0",
|
||||||
@@ -114,7 +107,6 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic
|
|||||||
"note": "1 tsp baking soda",
|
"note": "1 tsp baking soda",
|
||||||
"unit": None,
|
"unit": None,
|
||||||
"food": None,
|
"food": None,
|
||||||
"disableAmount": True,
|
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"originalText": None,
|
"originalText": None,
|
||||||
"referenceId": "64627441-39f9-4ee3-8494-bafe36451d12",
|
"referenceId": "64627441-39f9-4ee3-8494-bafe36451d12",
|
||||||
@@ -124,7 +116,6 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic
|
|||||||
"note": "1/2 tsp salt",
|
"note": "1/2 tsp salt",
|
||||||
"unit": None,
|
"unit": None,
|
||||||
"food": None,
|
"food": None,
|
||||||
"disableAmount": True,
|
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"originalText": None,
|
"originalText": None,
|
||||||
"referenceId": "7ae212d0-3cd1-44b0-899e-ec5bd91fd384",
|
"referenceId": "7ae212d0-3cd1-44b0-899e-ec5bd91fd384",
|
||||||
@@ -134,7 +125,6 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic
|
|||||||
"note": "1 cup cake flour",
|
"note": "1 cup cake flour",
|
||||||
"unit": None,
|
"unit": None,
|
||||||
"food": None,
|
"food": None,
|
||||||
"disableAmount": True,
|
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"originalText": None,
|
"originalText": None,
|
||||||
"referenceId": "06967994-8548-4952-a8cc-16e8db228ebd",
|
"referenceId": "06967994-8548-4952-a8cc-16e8db228ebd",
|
||||||
@@ -144,7 +134,6 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic
|
|||||||
"note": "2 cups all-purpose flour",
|
"note": "2 cups all-purpose flour",
|
||||||
"unit": None,
|
"unit": None,
|
||||||
"food": None,
|
"food": None,
|
||||||
"disableAmount": True,
|
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"originalText": None,
|
"originalText": None,
|
||||||
"referenceId": "bdb33b23-c767-4465-acf8-3b8e79eb5691",
|
"referenceId": "bdb33b23-c767-4465-acf8-3b8e79eb5691",
|
||||||
@@ -154,7 +143,6 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic
|
|||||||
"note": "2 cups peanut butter chips",
|
"note": "2 cups peanut butter chips",
|
||||||
"unit": None,
|
"unit": None,
|
||||||
"food": None,
|
"food": None,
|
||||||
"disableAmount": True,
|
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"originalText": None,
|
"originalText": None,
|
||||||
"referenceId": "12ba0af8-affd-4fb2-9cca-6f1b3e8d3aef",
|
"referenceId": "12ba0af8-affd-4fb2-9cca-6f1b3e8d3aef",
|
||||||
@@ -164,7 +152,6 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic
|
|||||||
"note": "1½ cups Reese's Pieces candies",
|
"note": "1½ cups Reese's Pieces candies",
|
||||||
"unit": None,
|
"unit": None,
|
||||||
"food": None,
|
"food": None,
|
||||||
"disableAmount": True,
|
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"originalText": None,
|
"originalText": None,
|
||||||
"referenceId": "4bdc0598-a3eb-41ee-8af0-4da9348fbfe2",
|
"referenceId": "4bdc0598-a3eb-41ee-8af0-4da9348fbfe2",
|
||||||
@@ -221,7 +208,6 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic
|
|||||||
"showAssets": False,
|
"showAssets": False,
|
||||||
"landscapeView": False,
|
"landscapeView": False,
|
||||||
"disableComments": False,
|
"disableComments": False,
|
||||||
"disableAmount": True,
|
|
||||||
"locked": False,
|
"locked": False,
|
||||||
},
|
},
|
||||||
"assets": [],
|
"assets": [],
|
||||||
|
|||||||
75
dev/scripts/convert_seed_files_to_new_format.py
Normal file
75
dev/scripts/convert_seed_files_to_new_format.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import glob
|
||||||
|
import json
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
|
def get_seed_locale_names() -> set[str]:
|
||||||
|
"""Find all locales in the seed/resources/ folder
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A set of every file name where there's both a seed label and seed food file
|
||||||
|
"""
|
||||||
|
|
||||||
|
LABELS_PATH = "/workspaces/mealie/mealie/repos/seed/resources/labels/locales/"
|
||||||
|
FOODS_PATH = "/workspaces/mealie/mealie/repos/seed/resources/foods/locales/"
|
||||||
|
label_locales = glob.glob("*.json", root_dir=LABELS_PATH)
|
||||||
|
foods_locales = glob.glob("*.json", root_dir=FOODS_PATH)
|
||||||
|
|
||||||
|
# ensure that a locale has both a label and a food seed file
|
||||||
|
return set(label_locales).intersection(foods_locales)
|
||||||
|
|
||||||
|
|
||||||
|
def get_labels_from_file(locale: str) -> list[str]:
|
||||||
|
"""Query a locale to get all of the labels so that they can be added to the new foods seed format
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
All of the labels found within the seed file for a given locale
|
||||||
|
"""
|
||||||
|
|
||||||
|
locale_path = pathlib.Path("/workspaces/mealie/mealie/repos/seed/resources/labels/locales/" + locale)
|
||||||
|
label_names = [label["name"] for label in json.loads(locale_path.read_text(encoding="utf-8"))]
|
||||||
|
return label_names
|
||||||
|
|
||||||
|
|
||||||
|
def transform_foods(locale: str):
|
||||||
|
"""
|
||||||
|
Convert the current food seed file for a locale into a new format which maps each food to a label
|
||||||
|
|
||||||
|
Existing format of foods seed file is a dictionary where each key is a food name and the values are a dictionary
|
||||||
|
of attributes such as name and plural_name
|
||||||
|
|
||||||
|
New format maps each food to a label. The top-level dictionary has each key as a label e.g. "Fruits".
|
||||||
|
Each label key as a value that is a dictionary with an element called "foods"
|
||||||
|
"Foods" is a dictionary of each food for that label, with a key of the english food name e.g. "baking-soda"
|
||||||
|
and a value of attributes, including the translated name of the item e.g. "bicarbonate of soda" for en-GB.
|
||||||
|
"""
|
||||||
|
|
||||||
|
locale_path = pathlib.Path("/workspaces/mealie/mealie/repos/seed/resources/foods/locales/" + locale)
|
||||||
|
|
||||||
|
with open(locale_path, encoding="utf-8") as infile:
|
||||||
|
data = json.load(infile)
|
||||||
|
|
||||||
|
first_value = next(iter(data.values()))
|
||||||
|
if isinstance(first_value, dict) and "foods" in first_value:
|
||||||
|
# Locale is already in the new format, skipping transformation
|
||||||
|
return
|
||||||
|
|
||||||
|
transformed_data = {"": {"foods": dict(data.items())}}
|
||||||
|
|
||||||
|
# Seeding for labels now pulls from the foods file and parses the labels from there (as top-level keys),
|
||||||
|
# thus we need to add all of the existing labels to the new food seed file and give them an empty foods dictionary
|
||||||
|
label_names = get_labels_from_file(locale)
|
||||||
|
for label in label_names:
|
||||||
|
transformed_data[label] = {"foods": {}}
|
||||||
|
|
||||||
|
with open(locale_path, "w", encoding="utf-8") as outfile:
|
||||||
|
json.dump(transformed_data, outfile, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
for locale in get_seed_locale_names():
|
||||||
|
transform_foods(locale)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
###############################################
|
###############################################
|
||||||
# Frontend Build
|
# Frontend Build
|
||||||
###############################################
|
###############################################
|
||||||
FROM node:16 AS frontend-builder
|
FROM node:24@sha256:7f80506b8225bcce2ce8202b1026fcde8f0bfb716b1b833f20250d79d4463276 \
|
||||||
|
AS frontend-builder
|
||||||
|
|
||||||
WORKDIR /frontend
|
WORKDIR /frontend
|
||||||
|
|
||||||
@@ -20,7 +21,8 @@ RUN yarn generate
|
|||||||
###############################################
|
###############################################
|
||||||
# Base Image - Python
|
# Base Image - Python
|
||||||
###############################################
|
###############################################
|
||||||
FROM python:3.12-slim as python-base
|
FROM python:3.12-slim@sha256:2267adc248a477c1f1a852a07a5a224d42abe54c28aafa572efa157dfb001bba \
|
||||||
|
AS python-base
|
||||||
|
|
||||||
ENV MEALIE_HOME="/app"
|
ENV MEALIE_HOME="/app"
|
||||||
|
|
||||||
@@ -48,40 +50,29 @@ RUN apt-get update \
|
|||||||
curl \
|
curl \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV POETRY_HOME="/opt/poetry" \
|
RUN pip install uv
|
||||||
POETRY_NO_INTERACTION=1
|
|
||||||
|
|
||||||
# prepend poetry to path
|
|
||||||
ENV PATH="$POETRY_HOME/bin:$PATH"
|
|
||||||
|
|
||||||
# install poetry - respects $POETRY_VERSION & $POETRY_HOME
|
|
||||||
ENV POETRY_VERSION=2.0.1
|
|
||||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
|
||||||
|
|
||||||
# install poetry plugins needed to build the package
|
|
||||||
RUN poetry self add "poetry-plugin-export>=1.9"
|
|
||||||
|
|
||||||
WORKDIR /mealie
|
WORKDIR /mealie
|
||||||
|
|
||||||
# copy project files here to ensure they will be cached.
|
# copy project files here to ensure they will be cached.
|
||||||
COPY poetry.lock pyproject.toml ./
|
COPY uv.lock pyproject.toml ./
|
||||||
COPY mealie ./mealie
|
COPY mealie ./mealie
|
||||||
|
|
||||||
# Copy frontend to package it into the wheel
|
# Copy frontend to package it into the wheel
|
||||||
COPY --from=frontend-builder /frontend/dist ./mealie/frontend
|
COPY --from=frontend-builder /frontend/dist ./mealie/frontend
|
||||||
|
|
||||||
# Build the source and binary package
|
# Build the source and binary package
|
||||||
RUN poetry build --output=dist
|
RUN uv build --out-dir dist
|
||||||
|
|
||||||
# Create the requirements file, which is used to install the built package and
|
# Create the requirements file, which is used to install the built package and
|
||||||
# its pinned dependencies later. mealie is included to ensure the built one is
|
# its pinned dependencies later. mealie is included to ensure the built one is
|
||||||
# what's installed.
|
# what's installed.
|
||||||
RUN export MEALIE_VERSION=$(poetry version --short) \
|
RUN uv export --no-editable --no-emit-project --extra pgsql --format requirements-txt --output-file dist/requirements.txt \
|
||||||
&& poetry export --only=main --extras=pgsql --output=dist/requirements.txt \
|
&& MEALIE_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])") \
|
||||||
&& echo "mealie[pgsql]==$MEALIE_VERSION \\" >> dist/requirements.txt \
|
&& echo "mealie[pgsql]==${MEALIE_VERSION} \\" >> dist/requirements.txt \
|
||||||
&& poetry run pip hash dist/mealie-$MEALIE_VERSION-py3-none-any.whl | tail -n1 | tr -d '\n' >> dist/requirements.txt \
|
&& pip hash dist/mealie-${MEALIE_VERSION}-py3-none-any.whl | tail -n1 | tr -d '\n' >> dist/requirements.txt \
|
||||||
&& echo " \\" >> dist/requirements.txt \
|
&& echo " \\" >> dist/requirements.txt \
|
||||||
&& poetry run pip hash dist/mealie-$MEALIE_VERSION.tar.gz | tail -n1 >> dist/requirements.txt
|
&& pip hash dist/mealie-${MEALIE_VERSION}.tar.gz | tail -n1 >> dist/requirements.txt
|
||||||
|
|
||||||
###############################################
|
###############################################
|
||||||
# Package Container
|
# Package Container
|
||||||
@@ -119,7 +110,7 @@ RUN . $VENV_PATH/bin/activate \
|
|||||||
###############################################
|
###############################################
|
||||||
# Production Image
|
# Production Image
|
||||||
###############################################
|
###############################################
|
||||||
FROM python-base as production
|
FROM python-base AS production
|
||||||
LABEL org.opencontainers.image.source="https://github.com/mealie-recipes/mealie"
|
LABEL org.opencontainers.image.source="https://github.com/mealie-recipes/mealie"
|
||||||
ENV PRODUCTION=true
|
ENV PRODUCTION=true
|
||||||
ENV TESTING=false
|
ENV TESTING=false
|
||||||
@@ -132,7 +123,7 @@ RUN apt-get update \
|
|||||||
gosu \
|
gosu \
|
||||||
iproute2 \
|
iproute2 \
|
||||||
libldap-common \
|
libldap-common \
|
||||||
libldap-2.5 \
|
libldap2 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# create directory used for Docker Secrets
|
# create directory used for Docker Secrets
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ yarnpkg generate
|
|||||||
popd
|
popd
|
||||||
rm -r mealie/frontend
|
rm -r mealie/frontend
|
||||||
cp -a frontend/dist mealie/frontend
|
cp -a frontend/dist mealie/frontend
|
||||||
poetry build
|
uv build --out-dir dist
|
||||||
poetry export -n --only=main --extras=pgsql --output=dist/requirements.txt
|
uv export --no-editable --no-emit-project --extra pgsql --format requirements-txt --output-file dist/requirements.txt
|
||||||
MEALIE_VERSION=$(poetry version --short)
|
MEALIE_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
||||||
echo "mealie[pgsql]==${MEALIE_VERSION} \\" >> dist/requirements.txt
|
echo "mealie[pgsql]==${MEALIE_VERSION} \\" >> dist/requirements.txt
|
||||||
poetry run pip hash dist/mealie-${MEALIE_VERSION}-py3-none-any.whl | tail -n1 | tr -d '\n' >> dist/requirements.txt
|
pip hash dist/mealie-${MEALIE_VERSION}-py3-none-any.whl | tail -n1 | tr -d '\n' >> dist/requirements.txt
|
||||||
echo " \\" >> dist/requirements.txt
|
echo " \\" >> dist/requirements.txt
|
||||||
poetry run pip hash dist/mealie-${MEALIE_VERSION}.tar.gz | tail -n1 >> dist/requirements.txt
|
pip hash dist/mealie-${MEALIE_VERSION}.tar.gz | tail -n1 >> dist/requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
The Python package can be installed with all of its dependencies pinned to the versions tested by the developers with:
|
The Python package can be installed with all of its dependencies pinned to the versions tested by the developers with:
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ Make sure the VSCode Dev Containers extension is installed, then select "Dev Con
|
|||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- [Python 3.12](https://www.python.org/downloads/)
|
- [Python 3.12](https://www.python.org/downloads/)
|
||||||
- [Poetry](https://python-poetry.org/docs/#installation)
|
- [uv](https://docs.astral.sh/uv/)
|
||||||
- [Node v16.x](https://nodejs.org/en/)
|
- [Node](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)
|
- [task](https://taskfile.dev/#/installation)
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ Once the prerequisites are installed you can cd into the project base directory
|
|||||||
=== "Linux / macOS"
|
=== "Linux / macOS"
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Naviate To The Root Directory
|
# Navigate To The Root Directory
|
||||||
cd /path/to/project
|
cd /path/to/project
|
||||||
|
|
||||||
# Utilize the Taskfile to Install Dependencies
|
# Utilize the Taskfile to Install Dependencies
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
!!! 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!
|
||||||
|
|
||||||
Mealie supports adding the ingredients of a recipe to your [Bring](https://www.getbring.com/) shopping list, as you can
|
Mealie supports adding the ingredients of a recipe to your [Bring](https://www.getbring.com/) shopping list, as you can
|
||||||
see [here](https://docs.mealie.io/documentation/getting-started/features/#recipe-actions).
|
see [here](https://docs.mealie.io/documentation/getting-started/features/#recipe-actions).
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
!!! 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!
|
||||||
|
|
||||||
In a lot of ways, Home Assistant is why this project exists! Since Mealie has a robust API it makes it a great fit for interacting with Home Assistant and pulling information into your dashboard.
|
In a lot of ways, Home Assistant is why this project exists! Since Mealie has a robust API it makes it a great fit for interacting with Home Assistant and pulling information into your dashboard.
|
||||||
|
|
||||||
### Display Today's Meal in Lovelace
|
## Display Today's Meal in Lovelace
|
||||||
|
|
||||||
You can use the Mealie API to get access to meal plans in Home Assistant like in the image below.
|
You can use the Mealie API to get access to meal plans in Home Assistant like in the image below.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Steps:
|
## Steps:
|
||||||
|
|
||||||
#### 1. Get your API Token
|
### 1. Get your API Token
|
||||||
|
|
||||||
Create an API token from Mealie's User Settings page (https://hay-kot.github.io/mealie/documentation/users-groups/user-settings/#api-key-generation)
|
Create an API token from Mealie's User Settings page (see [this page](https://docs.mealie.io/documentation/getting-started/api-usage/#getting-a-token) to learn how).
|
||||||
|
|
||||||
#### 2. Create Home Assistant Sensors
|
### 2. Create Home Assistant Sensors
|
||||||
|
|
||||||
Create REST sensors in home assistant to get the details of today's meal.
|
Create REST sensors in home assistant to get the details of today's meal.
|
||||||
We will create sensors to get the name and ID of the first meal in today's meal plan (note that this may not be what is wanted if there is more than one meal planned for the day). We need the ID as well as the name to be able to retrieve the image for the meal.
|
We will create sensors to get the name and ID of the first meal in today's meal plan (note that this may not be what is wanted if there is more than one meal planned for the day). We need the ID as well as the name to be able to retrieve the image for the meal.
|
||||||
|
|
||||||
Make sure the url and port (`http://mealie:9000` ) matches your installation's address and _API_ port.
|
Make sure the url and port (`http://mealie:9000`) matches your installation's address and _API_ port.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
rest:
|
rest:
|
||||||
@@ -40,7 +40,7 @@ rest:
|
|||||||
unique_id: mealie_todays_meal_id
|
unique_id: mealie_todays_meal_id
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 3. Create a Camera Entity
|
### 3. Create a Camera Entity
|
||||||
|
|
||||||
We will create a camera entity to display the image of today's meal in Lovelace.
|
We will create a camera entity to display the image of today's meal in Lovelace.
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ In the still image url field put in:
|
|||||||
Under the entity page for the new camera, rename it.
|
Under the entity page for the new camera, rename it.
|
||||||
e.g. `camera.mealie_todays_meal_image`
|
e.g. `camera.mealie_todays_meal_image`
|
||||||
|
|
||||||
#### 4. Create a Lovelace Card
|
### 4. Create a Lovelace Card
|
||||||
|
|
||||||
Create a picture entity card and set the entity to `mealie_todays_meal` and the camera entity to `camera.mealie_todays_meal_image` or set in the yaml directly.
|
Create a picture entity card and set the entity to `mealie_todays_meal` and the camera entity to `camera.mealie_todays_meal_image` or set in the yaml directly.
|
||||||
|
|
||||||
@@ -76,4 +76,4 @@ card_mod:
|
|||||||
```
|
```
|
||||||
|
|
||||||
!!! tip
|
!!! tip
|
||||||
Due to how Home Assistant works with images, I had to include the additional styling to get the images to not appear distorted. This requires an [additional installation](https://github.com/thomasloven/lovelace-card-mod) from HACS.
|
Due to how Home Assistant works with images, I had to include the additional styling to get the images to not appear distorted. This requires an [additional installation](https://github.com/thomasloven/lovelace-card-mod) from HACS.
|
||||||
|
|||||||
@@ -12,12 +12,10 @@ var url = document.URL.endsWith('/') ?
|
|||||||
document.URL;
|
document.URL;
|
||||||
var mealie = "http://localhost:8080";
|
var mealie = "http://localhost:8080";
|
||||||
var group_slug = "home" // Change this to your group slug. You can obtain this from your URL after logging-in to Mealie
|
var group_slug = "home" // Change this to your group slug. You can obtain this from your URL after logging-in to Mealie
|
||||||
var use_keywords= "&use_keywords=1" // Optional - use keywords from recipe - update to "" if you don't want that
|
|
||||||
var edity = "&edit=1" // Optional - keep in edit mode - update to "" if you don't want that
|
|
||||||
|
|
||||||
if (mealie.slice(-1) === "/") {
|
if (mealie.slice(-1) === "/") {
|
||||||
mealie = mealie.slice(0, -1)
|
mealie = mealie.slice(0, -1)
|
||||||
}
|
}
|
||||||
var dest = mealie + "/g/" + group_slug + "/r/create/url?recipe_import_url=" + url + use_keywords + edity;
|
var dest = mealie + "/g/" + group_slug + "/r/create/url?recipe_import_url=" + url;
|
||||||
window.open(dest, "_blank");
|
window.open(dest, "_blank");
|
||||||
```
|
```
|
||||||
|
|||||||
29
docs/docs/documentation/community-guide/ios-shortcut.md
Normal file
29
docs/docs/documentation/community-guide/ios-shortcut.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
!!! info
|
||||||
|
This guide was submitted by a community member. Find something wrong? Submit a PR to get it fixed!
|
||||||
|
|
||||||
|
An easy way to add recipes to Mealie from an Apple device is via an Apple Shortcut. This is a short guide to install an configure a shortcut able to add recipes via a link or image(s).
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
If adding via images make sure to enable [Mealie's OpenAI Integration](https://docs.mealie.io/documentation/getting-started/installation/open-ai/)
|
||||||
|
|
||||||
|
## Javascript can only be run via Shortcuts on the Safari browser on MacOS and iOS. If you do not use Safari you may skip this section
|
||||||
|
Some sites have begun blocking AI scraping bots, inadvertently blocking the recipe scraping library Mealie uses as well. To circumvent this, the shortcut uses javascript to capture the raw html loaded in the browser and sends that to mealie when possible.
|
||||||
|
|
||||||
|
**iOS**
|
||||||
|
|
||||||
|
Settings app -> apps -> Shortcuts -> Advanced -> Allow Running Scripts
|
||||||
|
|
||||||
|
**MacOS**
|
||||||
|
|
||||||
|
Shortcuts app -> Settings (CMD ,) -> Advanced -> Allow Running Scripts
|
||||||
|
|
||||||
|
## Initial Setup
|
||||||
|
An API key is needed to authenticate with mealie. To create an api key for a user, navigate to http://YOUR_MEALIE_URL/user/profile/api-tokens. Alternatively you can create a key via the mealie home page by clicking the user's profile pic in the top left -> Api Tokens
|
||||||
|
|
||||||
|
The shortcut can be installed via **[This link](https://www.icloud.com/shortcuts/52834724050b42aebe0f2efd8d067360)**. Upon install, replace "MEALIE_API_KEY" with the API key generated previously and "MEALIE_URI" with the full URL used to access your mealie instance e.g. "http://10.0.0.5:9000" or "https://mealie.domain.com".
|
||||||
|
|
||||||
|
## Using the Shortcut
|
||||||
|
Once installed, the shortcut will automatically appear as an option when sharing an image or webpage. It can also be useful to add the shortcut to the home screen of your device. If selected from the home screen or shortcuts app, a menu will appear with prompts to import via **taking photo(s)**, **selecting photo(s)**, **scanning a URL**, or **pasting a URL**.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
Despite the Mealie API being able to accept multiple recipe images for import it is currently impossible to send multiple files in 1 web request via Shortcuts. Instead, the shortcut combines the images into a singular, vertically-concatenated image to send to mealie. This can result in slightly less-accurate text recognition.
|
||||||
@@ -1,71 +1,77 @@
|
|||||||
# Automating Backups with n8n
|
# Automating Backups with n8n
|
||||||
|
|
||||||
!!! 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!
|
||||||
|
|
||||||
> [n8n](https://github.com/n8n-io/n8n) is a free and source-available fair-code licensed workflow automation tool. Alternative to Zapier or Make, allowing you to use a UI to create automated workflows.
|
[n8n](https://github.com/n8n-io/n8n) is a free and source-available fair-code licensed workflow automation tool. It's an alternative to tools like Zapier or Make, allowing you to use a UI to create automated workflows.
|
||||||
|
|
||||||
This example workflow:
|
This example workflow:
|
||||||
|
|
||||||
1. Backups Mealie every morning via an API call
|
1. Creates a Mealie backup every morning via an API call
|
||||||
2. Deletes all but the last 7 backups
|
2. Keeps the last 7 backups, deleting older ones
|
||||||
|
|
||||||
> [!CAUTION]
|
!!! warning "Important"
|
||||||
> This only automates the backup function, this does not backup your data to anywhere except your local instance. Please make sure you are backing up your data to an external source.
|
This only automates the backup function, this does not backup your data to anywhere except your local instance. Please make sure you are backing up your data to an external source.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
# Setup
|
## Setup
|
||||||
|
|
||||||
## Deploying n8n
|
### Deploying n8n
|
||||||
|
|
||||||
Follow the relevant guide in the [n8n Documentation](https://docs.n8n.io/)
|
Follow the relevant guide in the [n8n Documentation](https://docs.n8n.io/)
|
||||||
|
|
||||||
## Importing n8n workflow
|
### Importing n8n workflow
|
||||||
|
|
||||||
1. In n8n, add a new workflow
|
1. In n8n, add a new workflow
|
||||||
2. In the top right hit the 3 dot menu and select 'Import from URL...'
|
2. In the top right hit the 3 dot menu and select 'Import from URL...'
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
3. Paste `https://github.com/mealie-recipes/mealie/blob/mealie-next/docs/docs/assets/other/n8n/n8n-mealie-backup.json` and click Import
|
3. Paste `https://github.com/mealie-recipes/mealie/blob/mealie-next/docs/docs/assets/other/n8n/n8n-mealie-backup.json` and click 'Import'
|
||||||
4. Click through the nodes and update the URLs for your environment
|
4. Click through the nodes and update the URLs for your environment
|
||||||
|
|
||||||
## API Credentials
|
### API Credentials
|
||||||
|
|
||||||
#### Generate Mealie API Token
|
#### Generate Mealie API Token
|
||||||
|
|
||||||
1. Head to https://mealie.example.com/user/profile/api-tokens
|
1. Head to `<YOUR MEALIE INSTANCE>/user/profile/api-tokens`
|
||||||
> If you dont see this screen make sure that "Show advanced features" is checked under https://mealie.example.com/user/profile/edit
|
|
||||||
2. Under token name, enter the name of the token i.e. 'n8n' and hit Generate
|
!!! tip
|
||||||
|
If you dont see this screen make sure that "Show advanced features" is checked under `<YOUR MEALIE INSTANCE>/user/profile/edit`
|
||||||
|
|
||||||
|
2. Under token name, enter the name of the token (for example, 'n8n') and hit 'Generate'
|
||||||
|
|
||||||
3. Copy and keep this API Token somewhere safe, this is like your password!
|
3. Copy and keep this API Token somewhere safe, this is like your password!
|
||||||
|
|
||||||
> You can use your normal user for this, but assuming you're an admin you could also choose to create a user named n8n and generate the API key against that user.
|
!!! tip
|
||||||
|
You can use your normal user for this, but assuming you're an admin you could also choose to create a user named n8n and generate the API key against that user.
|
||||||
|
|
||||||
#### Setup Credentials in n8n
|
#### Setup Credentials in n8n
|
||||||
|
|
||||||
> [n8n Docs](https://docs.n8n.io/credentials/add-edit-credentials/)
|
See also [n8n Docs](https://docs.n8n.io/credentials/add-edit-credentials/).
|
||||||
|
|
||||||
1. Create a new "Header Auth" Credential
|
1. Create a new "Header Auth" Credential
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
2. In the connection screen set - Name as `Authorization` - Value as `Bearer {INSERT MEALIE API KEY}`
|
2. In the connection screen set - Name as `Authorization` - Value as `Bearer {INSERT MEALIE API KEY}`
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
3. In the workflow you created, for the "Run Backup", "Get All backups", and "Delete Oldies" nodes, update:
|
3. In the workflow you created, for the "Run Backup", "Get All backups", and "Delete Oldies" nodes, update:
|
||||||
- Authentication to `Generic Credential Type`
|
|
||||||
- Generic Auth Type to `Header Auth`
|
|
||||||
- Header Auth to `Mealie API` or whatever you named your credentials
|
|
||||||
|
|
||||||

|
- Authentication to `Generic Credential Type`
|
||||||
|
- Generic Auth Type to `Header Auth`
|
||||||
|
- Header Auth to `Mealie API` or whatever you named your credentials
|
||||||
|
|
||||||
## Notification Node
|

|
||||||
|
|
||||||
> Please use error notifications of some kind. It's very easy to set and forget an automation, then have the worst happen and lose data.
|
### Notification Node
|
||||||
|
|
||||||
|
!!! warning "Important"
|
||||||
|
Please use error notifications of some kind. It's very easy to set and forget an automation, then have the worst happen and lose data.
|
||||||
|
|
||||||
[ntfy](https://github.com/binwiederhier/ntfy) is a great open source, self-hostable tool for sending notifications.
|
[ntfy](https://github.com/binwiederhier/ntfy) is a great open source, self-hostable tool for sending notifications.
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
# Using SWAG as Reverse Proxy
|
# Using SWAG as Reverse Proxy
|
||||||
|
|
||||||
!!! 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!
|
||||||
|
|
||||||
|
To make the setup of a Reverse Proxy much easier, Linuxserver.io developed [SWAG](https://github.com/linuxserver/docker-swag).
|
||||||
|
|
||||||
|
|
||||||
To make the setup of a Reverse Proxy much easier, Linuxserver.io developed [SWAG](https://github.com/linuxserver/docker-swag)
|
|
||||||
SWAG - Secure Web Application Gateway (formerly known as letsencrypt, no relation to Let's Encrypt™) sets up an Nginx web server and reverse proxy with PHP support and a built-in certbot client that automates free SSL server certificate generation and renewal processes (Let's Encrypt and ZeroSSL). It also contains fail2ban for intrusion prevention.
|
SWAG - Secure Web Application Gateway (formerly known as letsencrypt, no relation to Let's Encrypt™) sets up an Nginx web server and reverse proxy with PHP support and a built-in certbot client that automates free SSL server certificate generation and renewal processes (Let's Encrypt and ZeroSSL). It also contains fail2ban for intrusion prevention.
|
||||||
|
|
||||||
## Step 1: Get a domain
|
## Step 1: Get a domain
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ Before you can start using OIDC Authentication, you must first configure a new c
|
|||||||
|
|
||||||
Take the client id and your discovery URL and update your environment variables to include the required OIDC variables described in [Installation - Backend Configuration](../installation/backend-config.md#openid-connect-oidc).
|
Take the client id and your discovery URL and update your environment variables to include the required OIDC variables described in [Installation - Backend Configuration](../installation/backend-config.md#openid-connect-oidc).
|
||||||
|
|
||||||
|
You might also want to set ALLOW_PASSWORD_LOGIN to false, to hide the username+password inputs, if you want to allow logins only via OIDC.
|
||||||
|
|
||||||
### Groups
|
### Groups
|
||||||
|
|
||||||
There are two (optional) [environment variables](../installation/backend-config.md#openid-connect-oidc) that can control which of the users in your IdP can log in to Mealie and what permissions they will have. Keep in mind that these groups **do not necessarily correspond to groups in Mealie**. The groups claim is configurable via the `OIDC_GROUPS_CLAIM` environment variable. The groups should be **defined in your IdP** and be returned in the configured claim value.
|
There are two (optional) [environment variables](../installation/backend-config.md#openid-connect-oidc) that can control which of the users in your IdP can log in to Mealie and what permissions they will have. Keep in mind that these groups **do not necessarily correspond to groups in Mealie**. The groups claim is configurable via the `OIDC_GROUPS_CLAIM` environment variable. The groups should be **defined in your IdP** and be returned in the configured claim value.
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ Before you can start using OIDC Authentication, you must first configure a new c
|
|||||||
http://localhost:9091/login
|
http://localhost:9091/login
|
||||||
https://mealie.example.com/login
|
https://mealie.example.com/login
|
||||||
|
|
||||||
|
If you are hosting Mealie behind a reverse proxy (nginx, Caddy, ...) to terminate TLS, make sure to start Mealie's Gunicorn server
|
||||||
|
with `--forwarded-allow-ips=<ip-of-proxy>`, otherwise the `X-Forwarded-*` headers will be ignored and the generated OIDC redirect
|
||||||
|
URI will use the wrong scheme (http instead of https). This will lead to authentication errors with strict OIDC providers.
|
||||||
|
|
||||||
3. Configure origins
|
3. Configure origins
|
||||||
|
|
||||||
If your identity provider enforces CORS on any endpoints, you will need to specify your Mealie URL as an Allowed Origin.
|
If your identity provider enforces CORS on any endpoints, you will need to specify your Mealie URL as an Allowed Origin.
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
Mealie allows you to link ingredients to specific steps in a recipe, ensuring you know exactly when to add each ingredient during the cooking process.
|
Mealie allows you to link ingredients to specific steps in a recipe, ensuring you know exactly when to add each ingredient during the cooking process.
|
||||||
|
|
||||||
**Link Ingredients to Steps in a Recipe**
|
**Link Ingredients to Steps in a Recipe**
|
||||||
|
|
||||||
1. Go to a recipe
|
1. Go to a recipe
|
||||||
2. Click the Edit button/icon
|
2. Click the Edit button/icon
|
||||||
3. Scroll down to the step you want to link ingredients to
|
3. Scroll down to the step you want to link ingredients to
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
7. Click 'Save' on the Recipe
|
7. Click 'Save' on the Recipe
|
||||||
|
|
||||||
You can optionally link the same ingredient to multiple steps, which is useful for prepping an ingredient in one step and using it in another.
|
You can optionally link the same ingredient to multiple steps, which is useful for prepping an ingredient in one step and using it in another.
|
||||||
|
|
||||||
??? question "What is fuzzy search and how do I use it?"
|
??? question "What is fuzzy search and how do I use it?"
|
||||||
|
|
||||||
### What is fuzzy search and how do I use it?
|
### What is fuzzy search and how do I use it?
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
|
|
||||||
You can change the theme by settings the environment variables.
|
You can change the theme by settings the environment variables.
|
||||||
|
|
||||||
- [Backend Config - Themeing](./installation/backend-config.md#themeing)
|
- [Backend Config - Theming](./installation/backend-config.md#theming)
|
||||||
|
|
||||||
|
|
||||||
??? question "How can I change the login session timeout?"
|
??? question "How can I change the login session timeout?"
|
||||||
@@ -233,7 +233,7 @@
|
|||||||
|
|
||||||
### How can I use Mealie externally
|
### How can I use Mealie externally
|
||||||
|
|
||||||
Exposing Mealie or any service to the internet can pose significant security risks. Before proceeding, carefully evaluate the potential impacts on your system. Due to the unique nature of each network, we cannot provide specific steps for your setup.
|
Exposing Mealie or any service to the internet can pose significant security risks. Before proceeding, carefully evaluate the potential impacts on your system. Due to the unique nature of each network, we cannot provide specific steps for your setup.
|
||||||
|
|
||||||
There is a community guide available for one way to potentially set this up, and you could reach out on Discord for further discussion on what may be best for your network.
|
There is a community guide available for one way to potentially set this up, and you could reach out on Discord for further discussion on what may be best for your network.
|
||||||
|
|
||||||
@@ -267,7 +267,7 @@
|
|||||||
|
|
||||||
### Why setup Email?
|
### Why setup Email?
|
||||||
|
|
||||||
Mealie uses email to send account invites and password resets. If you don't use these features, you don't need to set up email. There are also other methods to perform these actions that do not require the setup of Email.
|
Mealie uses email to send account invites and password resets. If you don't use these features, you don't need to set up email. There are also other methods to perform these actions that do not require the setup of Email.
|
||||||
|
|
||||||
Email settings can be adjusted via environment variables on the backend container:
|
Email settings can be adjusted via environment variables on the backend container:
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ Mealie supports importing recipes from a few other sources besides websites. Cur
|
|||||||
- Recipe Keeper
|
- Recipe Keeper
|
||||||
- Copy Me That
|
- Copy Me That
|
||||||
- My Recipe Box
|
- My Recipe Box
|
||||||
|
- DVO Cook'n X3
|
||||||
|
|
||||||
You can access these options on your installation at the `/group/migrations` page on your installation. If you'd like to see another source added, feel free to request so on Github.
|
You can access these options on your installation at the `/group/migrations` page on your installation. If you'd like to see another source added, feel free to request so on Github.
|
||||||
|
|
||||||
@@ -87,6 +88,7 @@ The shopping lists feature is a great way to keep track of what you need to buy
|
|||||||
Managing shopping lists can be done from the Sidebar > Shopping Lists.
|
Managing shopping lists can be done from the Sidebar > Shopping Lists.
|
||||||
|
|
||||||
Here you will be able to:
|
Here you will be able to:
|
||||||
|
|
||||||
- See items already on the Shopping List
|
- See items already on the Shopping List
|
||||||
- See linked recipes with ingredients
|
- See linked recipes with ingredients
|
||||||
- Toggling via the 'Pot' icon will show you the linked recipe, allowing you to click to access it.
|
- Toggling via the 'Pot' icon will show you the linked recipe, allowing you to click to access it.
|
||||||
@@ -117,6 +119,7 @@ Mealie is designed to integrate with many different external services. There are
|
|||||||
### Notifiers
|
### Notifiers
|
||||||
|
|
||||||
Notifiers are event-driven notifications sent when specific actions are performed within Mealie. Some actions include:
|
Notifiers are event-driven notifications sent when specific actions are performed within Mealie. Some actions include:
|
||||||
|
|
||||||
- Creating / Updating a recipe
|
- Creating / Updating a recipe
|
||||||
- Adding items to a shopping list
|
- Adding items to a shopping list
|
||||||
- Creating a new mealplan
|
- Creating a new mealplan
|
||||||
@@ -198,6 +201,7 @@ Mealie lets you fully customize how you organize your users. You can use Groups
|
|||||||
Groups are fully isolated instances of Mealie. Think of a goup as a completely separate, fully self-contained site. There is no data shared between groups. Each group has its own users, recipes, tags, categories, etc. A user logged-in to one group cannot make any changes to another.
|
Groups are fully isolated instances of Mealie. Think of a goup as a completely separate, fully self-contained site. There is no data shared between groups. Each group has its own users, recipes, tags, categories, etc. A user logged-in to one group cannot make any changes to another.
|
||||||
|
|
||||||
Common use cases for groups include:
|
Common use cases for groups include:
|
||||||
|
|
||||||
- Hosting multiple instances of Mealie for others who want to keep their data private and secure
|
- Hosting multiple instances of Mealie for others who want to keep their data private and secure
|
||||||
- Creating completely isolated recipe pools
|
- Creating completely isolated recipe pools
|
||||||
|
|
||||||
@@ -206,6 +210,7 @@ Common use cases for groups include:
|
|||||||
Households are subdivisions within a single Group. Households maintain their own users and settings, while sharing their recipes with other households. Households also share organizers (tags, categories, etc.) with the entire group. Meal Plans, Shopping Lists, and Integrations are only accessible within a household.
|
Households are subdivisions within a single Group. Households maintain their own users and settings, while sharing their recipes with other households. Households also share organizers (tags, categories, etc.) with the entire group. Meal Plans, Shopping Lists, and Integrations are only accessible within a household.
|
||||||
|
|
||||||
Common use cases for households include:
|
Common use cases for households include:
|
||||||
|
|
||||||
- Sharing a common recipe pool amongst families
|
- Sharing a common recipe pool amongst families
|
||||||
- Maintaining separate meal plans and shopping lists from other households
|
- Maintaining separate meal plans and shopping lists from other households
|
||||||
- Maintaining separate integrations and customizations from other households
|
- Maintaining separate integrations and customizations from other households
|
||||||
|
|||||||
@@ -11,11 +11,12 @@
|
|||||||
| DEFAULT_GROUP | Home | The default group for users |
|
| DEFAULT_GROUP | Home | The default group for users |
|
||||||
| DEFAULT_HOUSEHOLD | Family | The default household for users in each group |
|
| DEFAULT_HOUSEHOLD | Family | The default household for users in each group |
|
||||||
| BASE_URL | http://localhost:8080 | Used for Notifications |
|
| BASE_URL | http://localhost:8080 | Used for Notifications |
|
||||||
| TOKEN_TIME | 48 | The time in hours that a login/auth token is valid |
|
| TOKEN_TIME | 48 | The time in hours that a login/auth token is valid. Must be <= 87600 (10 years, in hours). |
|
||||||
| API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** |
|
| API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** |
|
||||||
| API_DOCS | True | Turns on/off access to the API documentation locally |
|
| API_DOCS | True | Turns on/off access to the API documentation locally |
|
||||||
| TZ | UTC | Must be set to get correct date/time on the server |
|
| TZ | UTC | Must be set to get correct date/time on the server |
|
||||||
| ALLOW_SIGNUP<super>\*</super> | false | Allow user sign-up without token |
|
| ALLOW_SIGNUP<super>\*</super> | false | Allow user sign-up without token |
|
||||||
|
| ALLOW_PASSWORD_LOGIN | true | Whether or not to display the username+password input fields. Keep set to true unless you use OIDC authentication |
|
||||||
| LOG_CONFIG_OVERRIDE | | Override the config for logging with a custom path |
|
| LOG_CONFIG_OVERRIDE | | Override the config for logging with a custom path |
|
||||||
| LOG_LEVEL | info | Logging level (e.g. critical, error, warning, info, debug) |
|
| LOG_LEVEL | info | Logging level (e.g. critical, error, warning, info, debug) |
|
||||||
| DAILY_SCHEDULE_TIME | 23:45 | The time of day to run daily server tasks, in HH:MM format. Use the server's local time, *not* UTC |
|
| DAILY_SCHEDULE_TIME | 23:45 | The time of day to run daily server tasks, in HH:MM format. Use the server's local time, *not* UTC |
|
||||||
@@ -31,15 +32,16 @@
|
|||||||
|
|
||||||
### Database
|
### Database
|
||||||
|
|
||||||
| Variables | Default | Description |
|
| Variables | Default | Description |
|
||||||
| ------------------------------------------------------- | :------: | ----------------------------------------------------------------------- |
|
|---------------------------------------------------------|:--------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| DB_ENGINE | sqlite | Optional: 'sqlite', 'postgres' |
|
| DB_ENGINE | sqlite | Optional: 'sqlite', 'postgres' |
|
||||||
| POSTGRES_USER<super>[†][secrets]</super> | mealie | Postgres database user |
|
| SQLITE_MIGRATE_JOURNAL_WAL | False | If set to true, switches SQLite's journal mode to WAL, which allows for multiple concurrent accesses. This can be useful when you have a decent amount of concurrency or when using certain remote storage systems such as Ceph. |
|
||||||
| POSTGRES_PASSWORD<super>[†][secrets]</super> | mealie | Postgres database password |
|
| POSTGRES_USER<super>[†][secrets]</super> | mealie | Postgres database user |
|
||||||
| POSTGRES_SERVER<super>[†][secrets]</super> | postgres | Postgres database server address |
|
| POSTGRES_PASSWORD<super>[†][secrets]</super> | mealie | Postgres database password |
|
||||||
| POSTGRES_PORT<super>[†][secrets]</super> | 5432 | Postgres database port |
|
| POSTGRES_SERVER<super>[†][secrets]</super> | postgres | Postgres database server address |
|
||||||
| POSTGRES_DB<super>[†][secrets]</super> | mealie | Postgres database name |
|
| POSTGRES_PORT<super>[†][secrets]</super> | 5432 | Postgres database port |
|
||||||
| POSTGRES_URL_OVERRIDE<super>[†][secrets]</super> | None | Optional Postgres URL override to use instead of POSTGRES\_\* variables |
|
| POSTGRES_DB<super>[†][secrets]</super> | mealie | Postgres database name |
|
||||||
|
| POSTGRES_URL_OVERRIDE<super>[†][secrets]</super> | None | Optional Postgres URL override to use instead of POSTGRES\_\* variables |
|
||||||
|
|
||||||
### Email
|
### Email
|
||||||
|
|
||||||
@@ -130,12 +132,19 @@ For custom mapping variables (e.g. OPENAI_CUSTOM_HEADERS) you should pass values
|
|||||||
| OPENAI_ENABLE_IMAGE_SERVICES | True | Whether to enable OpenAI image services, such as creating recipes via image. Leave this enabled unless your custom model doesn't support it, or you want to reduce costs |
|
| OPENAI_ENABLE_IMAGE_SERVICES | True | Whether to enable OpenAI image services, such as creating recipes via image. Leave this enabled unless your custom model doesn't support it, or you want to reduce costs |
|
||||||
| OPENAI_WORKERS | 2 | Number of OpenAI workers per request. Higher values may increase processing speed, but will incur additional API costs |
|
| OPENAI_WORKERS | 2 | Number of OpenAI workers per request. Higher values may increase processing speed, but will incur additional API costs |
|
||||||
| OPENAI_SEND_DATABASE_DATA | True | Whether to send Mealie data to OpenAI to improve request accuracy. This will incur additional API costs |
|
| OPENAI_SEND_DATABASE_DATA | True | Whether to send Mealie data to OpenAI to improve request accuracy. This will incur additional API costs |
|
||||||
| OPENAI_REQUEST_TIMEOUT | 60 | The number of seconds to wait for an OpenAI request to complete before cancelling the request. Leave this empty unless you're running into timeout issues on slower hardware |
|
| OPENAI_REQUEST_TIMEOUT | 300 | The number of seconds to wait for an OpenAI request to complete before cancelling the request. Leave this empty unless you're running into timeout issues on slower hardware |
|
||||||
|
|
||||||
### Theming
|
### Theming
|
||||||
|
|
||||||
Setting the following environmental variables will change the theme of the frontend. Note that the themes are the same for all users. This is a break-change when migration from v0.x.x -> 1.x.x.
|
Setting the following environmental variables will change the theme of the frontend. Note that the themes are the same for all users. This is a break-change when migration from v0.x.x -> 1.x.x.
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
If you're setting these variables but not seeing these changes persist, try removing the `#` character. Also, depending on which syntax you're using, double-check you're using quotes correctly.
|
||||||
|
|
||||||
|
If using YAML mapping syntax, be sure to include quotes around these values, otherwise they will be treated as comments in your YAML file:<br>`THEME_LIGHT_PRIMARY: '#E58325'` or `THEME_LIGHT_PRIMARY: 'E58325'`
|
||||||
|
|
||||||
|
If using YAML sequence syntax, don't include any quotes:<br>`THEME_LIGHT_PRIMARY=#E58325` or `THEME_LIGHT_PRIMARY=E58325`
|
||||||
|
|
||||||
| Variables | Default | Description |
|
| Variables | Default | Description |
|
||||||
| --------------------- | :-----: | --------------------------- |
|
| --------------------- | :-----: | --------------------------- |
|
||||||
| THEME_LIGHT_PRIMARY | #E58325 | Light Theme Config Variable |
|
| THEME_LIGHT_PRIMARY | #E58325 | Light Theme Config Variable |
|
||||||
@@ -155,8 +164,6 @@ Setting the following environmental variables will change the theme of the front
|
|||||||
|
|
||||||
### Docker Secrets
|
### Docker Secrets
|
||||||
|
|
||||||
### Docker Secrets
|
|
||||||
|
|
||||||
> <super>†</super> Starting in version `2.4.2`, any environment variable in the preceding lists with a dagger
|
> <super>†</super> Starting in version `2.4.2`, any environment variable in the preceding lists with a dagger
|
||||||
> symbol next to them support the Docker Compose secrets pattern, below.
|
> symbol next to them support the Docker Compose secrets pattern, below.
|
||||||
[Docker Compose secrets][docker-secrets] can be used to secure sensitive information regarding the Mealie implementation
|
[Docker Compose secrets][docker-secrets] can be used to secure sensitive information regarding the Mealie implementation
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ To deploy mealie on your local network, it is highly recommended to use Docker t
|
|||||||
We've gone through a few versions of Mealie v1 deployment targets. We have settled on a single container deployment, and we've begun publishing the nightly container on github containers. If you're looking to move from the old nightly (split containers _or_ the omni image) to the new nightly, there are a few things you need to do:
|
We've gone through a few versions of Mealie v1 deployment targets. We have settled on a single container deployment, and we've begun publishing the nightly container on github containers. If you're looking to move from the old nightly (split containers _or_ the omni image) to the new nightly, there are a few things you need to do:
|
||||||
|
|
||||||
1. Take a backup just in case!
|
1. Take a backup just in case!
|
||||||
2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v2.8.0`
|
2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v3.4.0`
|
||||||
3. Take the external port from the frontend container and set that as the port mapped to port `9000` on the new container. The frontend is now served on port 9000 from the new container, so it will need to be mapped for you to have access.
|
3. Take the external port from the frontend container and set that as the port mapped to port `9000` on the new container. The frontend is now served on port 9000 from the new container, so it will need to be mapped for you to have access.
|
||||||
4. Restart the container
|
4. Restart the container
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ The following steps were tested on a Ubuntu 20.04 server, but should work for mo
|
|||||||
|
|
||||||
## Step 3: Customizing The `docker-compose.yaml` files.
|
## Step 3: Customizing The `docker-compose.yaml` files.
|
||||||
|
|
||||||
After you've decided setup the files it's important to set a few ENV variables to ensure that you can use all the features of Mealie. I recommend that you verify and check that:
|
After you've decided how to set up your files, it's important to set a few ENV variables to ensure that you can use all the features of Mealie. Verify that:
|
||||||
|
|
||||||
- [x] You've configured the relevant ENV variables for your database selection in the `docker-compose.yaml` files.
|
- [x] You've configured the relevant ENV variables for your database selection in the `docker-compose.yaml` files.
|
||||||
- [x] You've configured the [SMTP server settings](./backend-config.md#email) (used for invitations, password resets, etc). You can setup a [google app password](https://support.google.com/accounts/answer/185833?hl=en) if you want to send email via gmail.
|
- [x] You've configured the [SMTP server settings](./backend-config.md#email) (used for invitations, password resets, etc). You can setup a [google app password](https://support.google.com/accounts/answer/185833?hl=en) if you want to send email via gmail.
|
||||||
@@ -117,7 +117,7 @@ The latest tag provides the latest released image of Mealie.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**These tags no are long updated**
|
**These tags are no longer updated**
|
||||||
|
|
||||||
`mealie:frontend-v1.0.0beta-x` **and** `mealie:api-v1.0.0beta-x`
|
`mealie:frontend-v1.0.0beta-x` **and** `mealie:api-v1.0.0beta-x`
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# Installing with PostgreSQL
|
# Installing with PostgreSQL
|
||||||
|
|
||||||
|
!!! Warning
|
||||||
|
When upgrading postgresql major versions, manual steps are required [Postgres#37](https://github.com/docker-library/postgres/issues/37).
|
||||||
|
|
||||||
PostgreSQL might be considered if you need to support many concurrent users. In addition, some features are only enabled on PostgreSQL, such as fuzzy search.
|
PostgreSQL might be considered if you need to support many concurrent users. In addition, some features are only enabled on PostgreSQL, such as fuzzy search.
|
||||||
|
|
||||||
**For Environment Variable Configuration, see** [Backend Configuration](./backend-config.md)
|
**For Environment Variable Configuration, see** [Backend Configuration](./backend-config.md)
|
||||||
@@ -7,7 +10,7 @@ PostgreSQL might be considered if you need to support many concurrent users. In
|
|||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
mealie:
|
mealie:
|
||||||
image: ghcr.io/mealie-recipes/mealie:v2.8.0 # (3)
|
image: ghcr.io/mealie-recipes/mealie:v3.4.0 # (3)
|
||||||
container_name: mealie
|
container_name: mealie
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
@@ -38,7 +41,7 @@ services:
|
|||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
container_name: postgres
|
container_name: postgres
|
||||||
image: postgres:15
|
image: postgres:17
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- mealie-pgdata:/var/lib/postgresql/data
|
- mealie-pgdata:/var/lib/postgresql/data
|
||||||
@@ -46,6 +49,7 @@ services:
|
|||||||
POSTGRES_PASSWORD: mealie
|
POSTGRES_PASSWORD: mealie
|
||||||
POSTGRES_USER: mealie
|
POSTGRES_USER: mealie
|
||||||
PGUSER: mealie
|
PGUSER: mealie
|
||||||
|
POSTGRES_DB: mealie
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "pg_isready"]
|
test: ["CMD", "pg_isready"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ SQLite is a popular, open source, self-contained, zero-configuration database th
|
|||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
mealie:
|
mealie:
|
||||||
image: ghcr.io/mealie-recipes/mealie:v2.8.0 # (3)
|
image: ghcr.io/mealie-recipes/mealie:v3.4.0 # (3)
|
||||||
container_name: mealie
|
container_name: mealie
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ Mealie is a self hosted recipe manager and meal planner with a RestAPI backend a
|
|||||||
- Copy Me That
|
- Copy Me That
|
||||||
- Paprika
|
- Paprika
|
||||||
- Tandoor Recipes
|
- Tandoor Recipes
|
||||||
|
- DVO Cook'n X3
|
||||||
- Random Meal Plan generation
|
- Random Meal Plan generation
|
||||||
- Advanced rule configuration to fine tune random recipes
|
- Advanced rule configuration to fine tune random recipes
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,3 @@
|
|||||||
|
|
||||||
## Feature Requests
|
## Feature Requests
|
||||||
[Please request new features on Github](https://github.com/mealie-recipes/mealie/discussions/new?category=feature-request)
|
[Please request new features on Github](https://github.com/mealie-recipes/mealie/discussions/new?category=feature-request)
|
||||||
|
|
||||||
## Progress
|
|
||||||
See the [Github Projects page](https://github.com/users/hay-kot/projects/2) to see what is currently being worked on
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
You MUST read the release notes prior to upgrading your container. Mealie has a robust backup and restore system for managing your data. Pre-v1.0.0 versions of Mealie use a different database structure, so if you are upgrading from pre-v1.0.0 to v1.0.0, you MUST backup your data and then re-import it. Even if you are already on v1.0.0, it is strongly recommended to backup all data before updating.
|
You MUST read the release notes prior to upgrading your container. Mealie has a robust backup and restore system for managing your data. Pre-v1.0.0 versions of Mealie use a different database structure, so if you are upgrading from pre-v1.0.0 to v1.0.0, you MUST backup your data and then re-import it. Even if you are already on v1.0.0, it is strongly recommended to backup all data before updating.
|
||||||
|
|
||||||
### Before Upgrading
|
### Before Upgrading
|
||||||
- Read The Release Notes
|
- [Read The Release Notes](https://github.com/mealie-recipes/mealie/releases)
|
||||||
- Identify Breaking Changes
|
- Identify Breaking Changes
|
||||||
- Create a Backup and Download from the UI
|
- Create a Backup and Download from the UI
|
||||||
- Upgrade
|
- Upgrade
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Backups and Restores
|
# Backups and Restores
|
||||||
|
|
||||||
Mealie provides an integrated mechanic for doing full installation backups of the database.
|
Mealie provides an integrated mechanic for doing full installation backups of the database.
|
||||||
|
|
||||||
Navigate to Settings > Backups or manually by adding `/admin/backups` to your instance URL.
|
Navigate to Settings > Admin Settings > Backups or manually by adding `/admin/backups` to your instance URL.
|
||||||
|
|
||||||
From this page, you will be able to:
|
From this page, you will be able to:
|
||||||
|
|
||||||
- See a list of available backups
|
- See a list of available backups
|
||||||
- Create a backup
|
- Create a backup
|
||||||
@@ -39,7 +39,7 @@ Restoring the Database when using Postgres requires Mealie to be configured with
|
|||||||
```sql
|
```sql
|
||||||
ALTER USER mealie WITH SUPERUSER;
|
ALTER USER mealie WITH SUPERUSER;
|
||||||
|
|
||||||
# Run restore from Mealie
|
-- Run restore from Mealie
|
||||||
|
|
||||||
ALTER USER mealie WITH NOSUPERUSER;
|
ALTER USER mealie WITH NOSUPERUSER;
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Permissions and Public Access
|
# Permissions and Public Access
|
||||||
|
|
||||||
Mealie provides various levels of user access and permissions. This includes:
|
Mealie provides various levels of user access and permissions. This includes:
|
||||||
|
|
||||||
- Authentication and registration ([LDAP](../authentication/ldap.md) and [OpenID Connect](../authentication/oidc.md) are both supported)
|
- Authentication and registration ([LDAP](../authentication/ldap.md) and [OpenID Connect](../authentication/oidc.md) are both supported)
|
||||||
- Customizable user permissions
|
- Customizable user permissions
|
||||||
- Fine-tuned public access for non-users
|
- Fine-tuned public access for non-users
|
||||||
@@ -8,12 +9,12 @@ Mealie provides various levels of user access and permissions. This includes:
|
|||||||
## Customizable User Permissions
|
## Customizable User Permissions
|
||||||
|
|
||||||
Each user can be configured to have varying levels of access. Some of these permissions include:
|
Each user can be configured to have varying levels of access. Some of these permissions include:
|
||||||
|
|
||||||
- Access to Administrator tools
|
- Access to Administrator tools
|
||||||
- Access to inviting other users
|
- Access to inviting other users
|
||||||
- Access to manage their group and group data
|
- Access to manage their group and group data
|
||||||
|
|
||||||
Administrators can navigate to the Settings page and access the User Management page to configure these settings.
|
Administrators can configure these settings on the User Management page (navigate to Settings > Admin Settings > Users or append `/admin/manage/users` to your instance URL).
|
||||||
|
|
||||||
|
|
||||||
[User Management Demo](https://demo.mealie.io/admin/manage/users){ .md-button .md-button--primary }
|
[User Management Demo](https://demo.mealie.io/admin/manage/users){ .md-button .md-button--primary }
|
||||||
|
|
||||||
@@ -22,8 +23,8 @@ Administrators can navigate to the Settings page and access the User Management
|
|||||||
By default, groups and households are set to private, meaning only logged-in users may access the group/household. In order for a recipe to be viewable by public (not logged-in) users, three criteria must be met:
|
By default, groups and households are set to private, meaning only logged-in users may access the group/household. In order for a recipe to be viewable by public (not logged-in) users, three criteria must be met:
|
||||||
|
|
||||||
1. The group must not be private
|
1. The group must not be private
|
||||||
2. The household must not be private, *and* the household setting for allowing users outside of your group to see your recipes must be enabled. These can be toggled on the Household Settings page
|
2. The household must not be private, _and_ the household setting for allowing users outside of your group to see your recipes must be enabled. These can be toggled on the Household Management page (navigate to Settings > Admin Settings > Households or append `/admin/manage/households` to your instance URL)
|
||||||
2. The recipe must be set to public. This can be toggled for each recipe individually, or in bulk using the Recipe Data Management page
|
3. The recipe must be set to public. This can be toggled for each recipe individually, or in bulk using the Recipe Data Management page
|
||||||
|
|
||||||
Additionally, if the group is not private, public users can view all public group data (public recipes, public cookbooks, etc.) from the home page ([e.g. the demo home page](https://demo.mealie.io/g/home)).
|
Additionally, if the group is not private, public users can view all public group data (public recipes, public cookbooks, etc.) from the home page ([e.g. the demo home page](https://demo.mealie.io/g/home)).
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -351,7 +351,7 @@
|
|||||||
<!-- Custom narrow footer -->
|
<!-- Custom narrow footer -->
|
||||||
<div class="md-footer-meta__inner md-grid">
|
<div class="md-footer-meta__inner md-grid">
|
||||||
<div class="md-footer-social">
|
<div class="md-footer-social">
|
||||||
<a class="md-footer-social__link" href="https://github.com/hay-kot/mealie" rel="noopener" target="_blank"
|
<a class="md-footer-social__link" href="https://github.com/mealie-recipes/mealie" rel="noopener" target="_blank"
|
||||||
title="github.com">
|
title="github.com">
|
||||||
<svg style="width: 32px; height: 32px" viewBox="0 0 480 512" xmlns="http://www.w3.org/2000/svg">
|
<svg style="width: 32px; height: 32px" viewBox="0 0 480 512" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
|
|||||||
@@ -86,11 +86,11 @@ nav:
|
|||||||
|
|
||||||
- Community Guides:
|
- Community Guides:
|
||||||
- Bring API without internet exposure: "documentation/community-guide/bring-api.md"
|
- Bring API without internet exposure: "documentation/community-guide/bring-api.md"
|
||||||
- Automate Backups with n8n: "documentation/community-guide/n8n-backup-automation.md"
|
- Automating Backups with n8n: "documentation/community-guide/n8n-backup-automation.md"
|
||||||
- Bulk Url Import: "documentation/community-guide/bulk-url-import.md"
|
- Bulk Url Import: "documentation/community-guide/bulk-url-import.md"
|
||||||
- Home Assistant: "documentation/community-guide/home-assistant.md"
|
- Home Assistant: "documentation/community-guide/home-assistant.md"
|
||||||
- Import Bookmarklet: "documentation/community-guide/import-recipe-bookmarklet.md"
|
- Import Bookmarklet: "documentation/community-guide/import-recipe-bookmarklet.md"
|
||||||
- iOS Shortcuts: "documentation/community-guide/ios.md"
|
- iOS Shortcut: "documentation/community-guide/ios-shortcut.md"
|
||||||
- Reverse Proxy (SWAG): "documentation/community-guide/swag.md"
|
- Reverse Proxy (SWAG): "documentation/community-guide/swag.md"
|
||||||
|
|
||||||
- API Reference: "api/redoc.md"
|
- API Reference: "api/redoc.md"
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
parser: "vue-eslint-parser",
|
|
||||||
parserOptions: {
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
requireConfigFile: false,
|
|
||||||
tsConfigRootDir: __dirname,
|
|
||||||
project: ["./tsconfig.json"],
|
|
||||||
extraFileExtensions: [".vue"],
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
"@nuxtjs/eslint-config-typescript",
|
|
||||||
"plugin:nuxt/recommended",
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
|
||||||
// "plugin:prettier/recommended",
|
|
||||||
"prettier",
|
|
||||||
],
|
|
||||||
// Re-add once we use nuxt bridge
|
|
||||||
// See https://v3.nuxtjs.org/getting-started/bridge#update-nuxtconfig
|
|
||||||
ignorePatterns: ["nuxt.config.js", "lib/api/types/**/*.ts"],
|
|
||||||
plugins: ["prettier"],
|
|
||||||
// add your custom rules here
|
|
||||||
rules: {
|
|
||||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
|
||||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
|
||||||
quotes: ["error", "double"],
|
|
||||||
"vue/component-name-in-template-casing": ["error", "PascalCase"],
|
|
||||||
camelcase: 0,
|
|
||||||
"vue/singleline-html-element-content-newline": "off",
|
|
||||||
"vue/multiline-html-element-content-newline": "off",
|
|
||||||
"vue/no-mutating-props": "off",
|
|
||||||
"vue/no-v-text-v-html-on-component": "warn",
|
|
||||||
"vue/no-v-for-template-key-on-child": "off",
|
|
||||||
"vue/valid-v-slot": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
allowModifiers: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"@typescript-eslint/ban-ts-comment": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"ts-ignore": "allow-with-description",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"no-restricted-imports": [
|
|
||||||
"error",
|
|
||||||
{ paths: ["@vue/reactivity", "@vue/runtime-dom", "@vue/composition-api", "vue-demi"] },
|
|
||||||
],
|
|
||||||
|
|
||||||
// TODO Gradually activate all rules
|
|
||||||
// Allow Promise in onMounted
|
|
||||||
"@typescript-eslint/no-misused-promises": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
checksVoidReturn: {
|
|
||||||
arguments: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-call": "off",
|
|
||||||
"@typescript-eslint/no-floating-promises": "off",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,378 +0,0 @@
|
|||||||
/* cyrillic-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 100;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-100-cyrillic-ext1.woff2') format('woff2');
|
|
||||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
|
||||||
}
|
|
||||||
/* cyrillic */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 100;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-100-cyrillic2.woff2') format('woff2');
|
|
||||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
||||||
}
|
|
||||||
/* greek-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 100;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-100-greek-ext3.woff2') format('woff2');
|
|
||||||
unicode-range: U+1F00-1FFF;
|
|
||||||
}
|
|
||||||
/* greek */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 100;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-100-greek4.woff2') format('woff2');
|
|
||||||
unicode-range: U+0370-03FF;
|
|
||||||
}
|
|
||||||
/* vietnamese */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 100;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-100-vietnamese5.woff2') format('woff2');
|
|
||||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
|
||||||
}
|
|
||||||
/* latin-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 100;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-100-latin-ext6.woff2') format('woff2');
|
|
||||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
||||||
}
|
|
||||||
/* latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 100;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-100-latin7.woff2') format('woff2');
|
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
||||||
}
|
|
||||||
/* cyrillic-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 300;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-300-cyrillic-ext8.woff2') format('woff2');
|
|
||||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
|
||||||
}
|
|
||||||
/* cyrillic */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 300;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-300-cyrillic9.woff2') format('woff2');
|
|
||||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
||||||
}
|
|
||||||
/* greek-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 300;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-300-greek-ext10.woff2') format('woff2');
|
|
||||||
unicode-range: U+1F00-1FFF;
|
|
||||||
}
|
|
||||||
/* greek */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 300;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-300-greek11.woff2') format('woff2');
|
|
||||||
unicode-range: U+0370-03FF;
|
|
||||||
}
|
|
||||||
/* vietnamese */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 300;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-300-vietnamese12.woff2') format('woff2');
|
|
||||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
|
||||||
}
|
|
||||||
/* latin-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 300;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-300-latin-ext13.woff2') format('woff2');
|
|
||||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
||||||
}
|
|
||||||
/* latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 300;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-300-latin14.woff2') format('woff2');
|
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
||||||
}
|
|
||||||
/* cyrillic-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-400-cyrillic-ext15.woff2') format('woff2');
|
|
||||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
|
||||||
}
|
|
||||||
/* cyrillic */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-400-cyrillic16.woff2') format('woff2');
|
|
||||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
||||||
}
|
|
||||||
/* greek-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-400-greek-ext17.woff2') format('woff2');
|
|
||||||
unicode-range: U+1F00-1FFF;
|
|
||||||
}
|
|
||||||
/* greek */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-400-greek18.woff2') format('woff2');
|
|
||||||
unicode-range: U+0370-03FF;
|
|
||||||
}
|
|
||||||
/* vietnamese */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-400-vietnamese19.woff2') format('woff2');
|
|
||||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
|
||||||
}
|
|
||||||
/* latin-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-400-latin-ext20.woff2') format('woff2');
|
|
||||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
||||||
}
|
|
||||||
/* latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-400-latin21.woff2') format('woff2');
|
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
||||||
}
|
|
||||||
/* cyrillic-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-500-cyrillic-ext22.woff2') format('woff2');
|
|
||||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
|
||||||
}
|
|
||||||
/* cyrillic */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-500-cyrillic23.woff2') format('woff2');
|
|
||||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
||||||
}
|
|
||||||
/* greek-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-500-greek-ext24.woff2') format('woff2');
|
|
||||||
unicode-range: U+1F00-1FFF;
|
|
||||||
}
|
|
||||||
/* greek */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-500-greek25.woff2') format('woff2');
|
|
||||||
unicode-range: U+0370-03FF;
|
|
||||||
}
|
|
||||||
/* vietnamese */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-500-vietnamese26.woff2') format('woff2');
|
|
||||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
|
||||||
}
|
|
||||||
/* latin-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-500-latin-ext27.woff2') format('woff2');
|
|
||||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
||||||
}
|
|
||||||
/* latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-500-latin28.woff2') format('woff2');
|
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
||||||
}
|
|
||||||
/* cyrillic-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-700-cyrillic-ext29.woff2') format('woff2');
|
|
||||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
|
||||||
}
|
|
||||||
/* cyrillic */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-700-cyrillic30.woff2') format('woff2');
|
|
||||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
||||||
}
|
|
||||||
/* greek-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-700-greek-ext31.woff2') format('woff2');
|
|
||||||
unicode-range: U+1F00-1FFF;
|
|
||||||
}
|
|
||||||
/* greek */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-700-greek32.woff2') format('woff2');
|
|
||||||
unicode-range: U+0370-03FF;
|
|
||||||
}
|
|
||||||
/* vietnamese */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-700-vietnamese33.woff2') format('woff2');
|
|
||||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
|
||||||
}
|
|
||||||
/* latin-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-700-latin-ext34.woff2') format('woff2');
|
|
||||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
||||||
}
|
|
||||||
/* latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-700-latin35.woff2') format('woff2');
|
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
||||||
}
|
|
||||||
/* cyrillic-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 900;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-900-cyrillic-ext36.woff2') format('woff2');
|
|
||||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
|
||||||
}
|
|
||||||
/* cyrillic */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 900;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-900-cyrillic37.woff2') format('woff2');
|
|
||||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
||||||
}
|
|
||||||
/* greek-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 900;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-900-greek-ext38.woff2') format('woff2');
|
|
||||||
unicode-range: U+1F00-1FFF;
|
|
||||||
}
|
|
||||||
/* greek */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 900;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-900-greek39.woff2') format('woff2');
|
|
||||||
unicode-range: U+0370-03FF;
|
|
||||||
}
|
|
||||||
/* vietnamese */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 900;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-900-vietnamese40.woff2') format('woff2');
|
|
||||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
|
||||||
}
|
|
||||||
/* latin-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 900;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-900-latin-ext41.woff2') format('woff2');
|
|
||||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
||||||
}
|
|
||||||
/* latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 900;
|
|
||||||
font-display: swap;
|
|
||||||
src: url('~assets/fonts/Roboto-900-latin42.woff2') format('woff2');
|
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -17,11 +17,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.theme--dark.v-application {
|
.theme--dark.v-application {
|
||||||
background-color: var(--v-background-base, #1e1e1e) !important;
|
background-color: rgb(var(--v-theme-background, 30, 30, 30)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme--dark.v-navigation-drawer {
|
.theme--dark.v-navigation-drawer {
|
||||||
background-color: var(--v-background-base, #1e1e1e) !important;
|
background-color: rgb(var(--v-theme-background, 30, 30, 30)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme--dark.v-card {
|
.theme--dark.v-card {
|
||||||
@@ -29,15 +29,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.left-border {
|
.left-border {
|
||||||
border-left: 5px solid var(--v-primary-base) !important;
|
border-left: 5px solid rgb(var(--v-theme-primary)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-warning-border {
|
.left-warning-border {
|
||||||
border-left: 5px solid var(--v-warning-base) !important;
|
border-left: 5px solid rgb(var(--v-theme-warning)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.handle {
|
.handle {
|
||||||
cursor: grab;
|
cursor: grab !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
@@ -56,3 +56,15 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgb(var(--v-theme-primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.fill-height {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-simple-handler {
|
||||||
|
background-color: rgb(var(--v-theme-primary)) !important;
|
||||||
|
}
|
||||||
@@ -1,17 +1,41 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<v-card-text v-if="cookbook" class="px-1">
|
<v-card-text
|
||||||
<v-text-field v-model="cookbook.name" :label="$t('cookbook.cookbook-name')"></v-text-field>
|
v-if="cookbook"
|
||||||
<v-textarea v-model="cookbook.description" auto-grow :rows="2" :label="$t('recipe.description')"></v-textarea>
|
class="px-1"
|
||||||
|
>
|
||||||
|
<v-text-field
|
||||||
|
v-model="cookbook.name"
|
||||||
|
:label="$t('cookbook.cookbook-name')"
|
||||||
|
variant="underlined"
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
<v-textarea
|
||||||
|
v-model="cookbook.description"
|
||||||
|
auto-grow
|
||||||
|
:rows="2"
|
||||||
|
:label="$t('recipe.description')"
|
||||||
|
variant="underlined"
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
<QueryFilterBuilder
|
<QueryFilterBuilder
|
||||||
:field-defs="fieldDefs"
|
:field-defs="fieldDefs"
|
||||||
:initial-query-filter="cookbook.queryFilter"
|
:initial-query-filter="cookbook.queryFilter"
|
||||||
@input="handleInput"
|
@input="handleInput"
|
||||||
/>
|
/>
|
||||||
<v-switch v-model="cookbook.public" hide-details single-line>
|
<v-switch
|
||||||
|
v-model="cookbook.public"
|
||||||
|
hide-details
|
||||||
|
single-line
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{ $t('cookbook.public-cookbook') }}
|
{{ $t('cookbook.public-cookbook') }}
|
||||||
<HelpIcon small right class="ml-2">
|
<HelpIcon
|
||||||
|
size="small"
|
||||||
|
right
|
||||||
|
class="ml-2"
|
||||||
|
>
|
||||||
{{ $t('cookbook.public-cookbook-description') }}
|
{{ $t('cookbook.public-cookbook-description') }}
|
||||||
</HelpIcon>
|
</HelpIcon>
|
||||||
</template>
|
</template>
|
||||||
@@ -20,74 +44,54 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent, useContext } from "@nuxtjs/composition-api";
|
|
||||||
import { ReadCookBook } from "~/lib/api/types/cookbook";
|
|
||||||
import { Organizer } from "~/lib/api/types/non-generated";
|
import { Organizer } from "~/lib/api/types/non-generated";
|
||||||
import QueryFilterBuilder from "~/components/Domain/QueryFilterBuilder.vue";
|
import QueryFilterBuilder from "~/components/Domain/QueryFilterBuilder.vue";
|
||||||
import { FieldDefinition } from "~/composables/use-query-filter-builder";
|
import type { FieldDefinition } from "~/composables/use-query-filter-builder";
|
||||||
|
import type { ReadCookBook } from "~/lib/api/types/cookbook";
|
||||||
|
|
||||||
export default defineComponent({
|
const modelValue = defineModel<ReadCookBook>({ required: true });
|
||||||
components: { QueryFilterBuilder },
|
const i18n = useI18n();
|
||||||
props: {
|
const cookbook = toRef(modelValue);
|
||||||
cookbook: {
|
function handleInput(value: string | undefined) {
|
||||||
type: Object as () => ReadCookBook,
|
cookbook.value.queryFilterString = value || "";
|
||||||
required: true,
|
}
|
||||||
},
|
|
||||||
actions: {
|
const fieldDefs: FieldDefinition[] = [
|
||||||
type: Object as () => any,
|
{
|
||||||
required: true,
|
name: "recipe_category.id",
|
||||||
},
|
label: i18n.t("category.categories"),
|
||||||
|
type: Organizer.Category,
|
||||||
},
|
},
|
||||||
setup(props) {
|
{
|
||||||
const { i18n } = useContext();
|
name: "tags.id",
|
||||||
|
label: i18n.t("tag.tags"),
|
||||||
function handleInput(value: string | undefined) {
|
type: Organizer.Tag,
|
||||||
props.cookbook.queryFilterString = value || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldDefs: FieldDefinition[] = [
|
|
||||||
{
|
|
||||||
name: "recipe_category.id",
|
|
||||||
label: i18n.tc("category.categories"),
|
|
||||||
type: Organizer.Category,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tags.id",
|
|
||||||
label: i18n.tc("tag.tags"),
|
|
||||||
type: Organizer.Tag,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "recipe_ingredient.food.id",
|
|
||||||
label: i18n.tc("recipe.ingredients"),
|
|
||||||
type: Organizer.Food,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tools.id",
|
|
||||||
label: i18n.tc("tool.tools"),
|
|
||||||
type: Organizer.Tool,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "household_id",
|
|
||||||
label: i18n.tc("household.households"),
|
|
||||||
type: Organizer.Household,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "created_at",
|
|
||||||
label: i18n.tc("general.date-created"),
|
|
||||||
type: "date",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "updated_at",
|
|
||||||
label: i18n.tc("general.date-updated"),
|
|
||||||
type: "date",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleInput,
|
|
||||||
fieldDefs,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
{
|
||||||
|
name: "recipe_ingredient.food.id",
|
||||||
|
label: i18n.t("recipe.ingredients"),
|
||||||
|
type: Organizer.Food,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tools.id",
|
||||||
|
label: i18n.t("tool.tools"),
|
||||||
|
type: Organizer.Tool,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "household_id",
|
||||||
|
label: i18n.t("household.households"),
|
||||||
|
type: Organizer.Household,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "created_at",
|
||||||
|
label: i18n.t("general.date-created"),
|
||||||
|
type: "date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "updated_at",
|
||||||
|
label: i18n.t("general.date-updated"),
|
||||||
|
type: "date",
|
||||||
|
},
|
||||||
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,44 +7,56 @@
|
|||||||
width="100%"
|
width="100%"
|
||||||
max-width="1100px"
|
max-width="1100px"
|
||||||
:icon="$globals.icons.pages"
|
:icon="$globals.icons.pages"
|
||||||
:title="$tc('general.edit')"
|
:title="$t('general.edit')"
|
||||||
:submit-icon="$globals.icons.save"
|
:submit-icon="$globals.icons.save"
|
||||||
:submit-text="$tc('general.save')"
|
:submit-text="$t('general.save')"
|
||||||
:submit-disabled="!editTarget.queryFilterString"
|
:submit-disabled="!editTarget.queryFilterString"
|
||||||
|
can-submit
|
||||||
@submit="editCookbook"
|
@submit="editCookbook"
|
||||||
>
|
>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<CookbookEditor :cookbook="editTarget" :actions="actions" />
|
<CookbookEditor
|
||||||
|
v-model="editTarget"
|
||||||
|
/>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
||||||
<!-- Page -->
|
<v-container
|
||||||
<v-container v-if="book" fluid>
|
v-if="book"
|
||||||
<v-app-bar color="transparent" flat class="mt-n1">
|
class="my-0"
|
||||||
<v-icon large left> {{ $globals.icons.pages }} </v-icon>
|
>
|
||||||
<v-toolbar-title class="headline"> {{ book.name }} </v-toolbar-title>
|
<v-sheet
|
||||||
<v-spacer></v-spacer>
|
color="transparent"
|
||||||
<BaseButton
|
class="d-flex flex-column w-100 pa-0 ma-0"
|
||||||
v-if="canEdit"
|
elevation="0"
|
||||||
class="mx-1"
|
>
|
||||||
:edit="true"
|
<div class="d-flex align-center w-100 mb-2">
|
||||||
@click="handleEditCookbook"
|
<v-toolbar-title class="headline mb-0">
|
||||||
/>
|
<v-icon size="large" class="mr-3">
|
||||||
</v-app-bar>
|
{{ $globals.icons.pages }}
|
||||||
<v-card flat>
|
</v-icon>
|
||||||
<v-card-text class="py-0">
|
{{ book.name }}
|
||||||
|
</v-toolbar-title>
|
||||||
|
<BaseButton
|
||||||
|
v-if="canEdit"
|
||||||
|
class="mx-1"
|
||||||
|
:edit="true"
|
||||||
|
@click="handleEditCookbook"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="book.description" class="subtitle-1 text-grey-lighten-1 mb-2">
|
||||||
{{ book.description }}
|
{{ book.description }}
|
||||||
</v-card-text>
|
</div>
|
||||||
</v-card>
|
</v-sheet>
|
||||||
|
|
||||||
<v-container class="pa-0">
|
<v-container class="pa-0">
|
||||||
<RecipeCardSection
|
<RecipeCardSection
|
||||||
class="mb-5 mx-1"
|
class="mb-5 mx-1"
|
||||||
:recipes="recipes"
|
:recipes="recipes"
|
||||||
:query="{ cookbook: slug }"
|
:query="{ cookbook: slug }"
|
||||||
@sortRecipes="assignSorted"
|
@sort-recipes="assignSorted"
|
||||||
@replaceRecipes="replaceRecipes"
|
@replace-recipes="replaceRecipes"
|
||||||
@appendRecipes="appendRecipes"
|
@append-recipes="appendRecipes"
|
||||||
@delete="removeRecipe"
|
@delete="removeRecipe"
|
||||||
/>
|
/>
|
||||||
</v-container>
|
</v-container>
|
||||||
@@ -52,92 +64,67 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, defineComponent, useRoute, ref, useContext, useMeta, reactive, useRouter } from "@nuxtjs/composition-api";
|
import { useLazyRecipes } from "~/composables/recipes";
|
||||||
import { useLazyRecipes } from "~/composables/recipes";
|
import RecipeCardSection from "@/components/Domain/Recipe/RecipeCardSection.vue";
|
||||||
import RecipeCardSection from "@/components/Domain/Recipe/RecipeCardSection.vue";
|
import { useCookbookStore } from "~/composables/store/use-cookbook-store";
|
||||||
import { useCookbook, useCookbooks } from "~/composables/use-group-cookbooks";
|
import { useCookbook } from "~/composables/use-group-cookbooks";
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { RecipeCookBook } from "~/lib/api/types/cookbook";
|
import type { ReadCookBook } from "~/lib/api/types/cookbook";
|
||||||
import CookbookEditor from "~/components/Domain/Cookbook/CookbookEditor.vue";
|
import CookbookEditor from "~/components/Domain/Cookbook/CookbookEditor.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
const $auth = useMealieAuth();
|
||||||
components: { RecipeCardSection, CookbookEditor },
|
const { isOwnGroup } = useLoggedInState();
|
||||||
setup() {
|
|
||||||
const { $auth } = useContext();
|
|
||||||
const { isOwnGroup } = useLoggedInState();
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
const groupSlug = computed(() => route.params.groupSlug as string || $auth.user.value?.groupSlug || "");
|
||||||
|
|
||||||
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value);
|
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value);
|
||||||
const slug = route.value.params.slug;
|
const slug = route.params.slug as string;
|
||||||
const { getOne } = useCookbook(isOwnGroup.value ? null : groupSlug.value);
|
const { getOne } = useCookbook(isOwnGroup.value ? null : groupSlug.value);
|
||||||
const { actions } = useCookbooks();
|
const { actions } = useCookbookStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const tab = ref(null);
|
const book = getOne(slug);
|
||||||
const book = getOne(slug);
|
|
||||||
|
|
||||||
const isOwnHousehold = computed(() => {
|
const isOwnHousehold = computed(() => {
|
||||||
if (!($auth.user && book.value?.householdId)) {
|
if (!($auth.user.value && book.value?.householdId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $auth.user.householdId === book.value.householdId;
|
return $auth.user.value.householdId === book.value.householdId;
|
||||||
})
|
});
|
||||||
const canEdit = computed(() => isOwnGroup.value && isOwnHousehold.value);
|
const canEdit = computed(() => isOwnGroup.value && isOwnHousehold.value);
|
||||||
|
|
||||||
const dialogStates = reactive({
|
const dialogStates = reactive({
|
||||||
edit: false,
|
edit: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const editTarget = ref<RecipeCookBook | null>(null);
|
const editTarget = ref<ReadCookBook | null>(null);
|
||||||
function handleEditCookbook() {
|
function handleEditCookbook() {
|
||||||
dialogStates.edit = true;
|
dialogStates.edit = true;
|
||||||
editTarget.value = book.value;
|
editTarget.value = book.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function editCookbook() {
|
async function editCookbook() {
|
||||||
if (!editTarget.value) {
|
if (!editTarget.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const response = await actions.updateOne(editTarget.value);
|
const response = await actions.updateOne(editTarget.value);
|
||||||
|
|
||||||
if (response?.slug && book.value?.slug !== response?.slug) {
|
if (response?.slug && book.value?.slug !== response?.slug) {
|
||||||
// if name changed, redirect to new slug
|
// if name changed, redirect to new slug
|
||||||
router.push(`/g/${route.value.params.groupSlug}/cookbooks/${response?.slug}`);
|
router.push(`/g/${route.params.groupSlug}/cookbooks/${response?.slug}`);
|
||||||
} else {
|
}
|
||||||
// otherwise reload the page, since the recipe criteria changed
|
else {
|
||||||
router.go(0);
|
// otherwise reload the page, since the recipe criteria changed
|
||||||
}
|
router.go(0);
|
||||||
dialogStates.edit = false;
|
}
|
||||||
editTarget.value = null;
|
dialogStates.edit = false;
|
||||||
}
|
editTarget.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
useMeta(() => {
|
useSeoMeta({
|
||||||
return {
|
title: book?.value?.name || "Cookbook",
|
||||||
title: book?.value?.name || "Cookbook",
|
});
|
||||||
};
|
</script>
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
book,
|
|
||||||
slug,
|
|
||||||
tab,
|
|
||||||
appendRecipes,
|
|
||||||
assignSorted,
|
|
||||||
recipes,
|
|
||||||
removeRecipe,
|
|
||||||
replaceRecipes,
|
|
||||||
canEdit,
|
|
||||||
dialogStates,
|
|
||||||
editTarget,
|
|
||||||
handleEditCookbook,
|
|
||||||
editCookbook,
|
|
||||||
actions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
head: {}, // Must include for useMeta
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -7,55 +7,46 @@
|
|||||||
class="elevation-0"
|
class="elevation-0"
|
||||||
@click:row="downloadData"
|
@click:row="downloadData"
|
||||||
>
|
>
|
||||||
<template #item.expires="{ item }">
|
<template #[`item.expires`]="{ item }">
|
||||||
{{ getTimeToExpire(item.expires) }}
|
{{ getTimeToExpire(item.expires) }}
|
||||||
</template>
|
</template>
|
||||||
<template #item.actions="{ item }">
|
<template #[`item.actions`]="{ item }">
|
||||||
<BaseButton download small :download-url="`/api/recipes/bulk-actions/export/download?path=${item.path}`">
|
<BaseButton
|
||||||
</BaseButton>
|
download
|
||||||
|
size="small"
|
||||||
|
:download-url="`/api/recipes/bulk-actions/export/download?path=${item.path}`"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent, useContext } from "@nuxtjs/composition-api";
|
|
||||||
import { parseISO, formatDistanceToNow } from "date-fns";
|
import { parseISO, formatDistanceToNow } from "date-fns";
|
||||||
import { GroupDataExport } from "~/lib/api/types/group";
|
import type { GroupDataExport } from "~/lib/api/types/group";
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
exports: {
|
|
||||||
type: Array as () => GroupDataExport[],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const { i18n } = useContext();
|
|
||||||
|
|
||||||
const headers = [
|
defineProps<{
|
||||||
{ text: i18n.t("export.export"), value: "name" },
|
exports: GroupDataExport[];
|
||||||
{ text: i18n.t("export.file-name"), value: "filename" },
|
}>();
|
||||||
{ text: i18n.t("export.size"), value: "size" },
|
|
||||||
{ text: i18n.t("export.link-expires"), value: "expires" },
|
|
||||||
{ text: "", value: "actions" },
|
|
||||||
];
|
|
||||||
|
|
||||||
function getTimeToExpire(timeString: string) {
|
const i18n = useI18n();
|
||||||
const expiresAt = parseISO(timeString);
|
|
||||||
|
|
||||||
return formatDistanceToNow(expiresAt, {
|
const headers = [
|
||||||
addSuffix: false,
|
{ title: i18n.t("export.export"), value: "name" },
|
||||||
});
|
{ title: i18n.t("export.file-name"), value: "filename" },
|
||||||
}
|
{ title: i18n.t("export.size"), value: "size" },
|
||||||
|
{ title: i18n.t("export.link-expires"), value: "expires" },
|
||||||
|
{ title: "", value: "actions" },
|
||||||
|
];
|
||||||
|
|
||||||
function downloadData(_: any) {
|
function getTimeToExpire(timeString: string) {
|
||||||
console.log("Downloading data...");
|
const expiresAt = parseISO(timeString);
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return formatDistanceToNow(expiresAt, {
|
||||||
downloadData,
|
addSuffix: false,
|
||||||
headers,
|
});
|
||||||
getTimeToExpire,
|
}
|
||||||
};
|
|
||||||
},
|
function downloadData(_: any) {
|
||||||
});
|
console.log("Downloading data...");
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,36 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="preferences">
|
<div v-if="preferences">
|
||||||
<BaseCardSectionTitle :title="$tc('group.general-preferences')"></BaseCardSectionTitle>
|
<BaseCardSectionTitle :title="$t('group.general-preferences')" />
|
||||||
<v-checkbox v-model="preferences.privateGroup" class="mt-n4" :label="$t('group.private-group')"></v-checkbox>
|
<v-checkbox
|
||||||
|
v-model="preferences.privateGroup"
|
||||||
|
class="mt-n4"
|
||||||
|
:label="$t('group.private-group')"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent, computed } from "@nuxtjs/composition-api";
|
import type { ReadGroupPreferences } from "~/lib/api/types/user";
|
||||||
|
|
||||||
export default defineComponent({
|
const preferences = defineModel<ReadGroupPreferences>({ required: true });
|
||||||
props: {
|
|
||||||
value: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, context) {
|
|
||||||
const preferences = computed({
|
|
||||||
get() {
|
|
||||||
return props.value;
|
|
||||||
},
|
|
||||||
set(val) {
|
|
||||||
context.emit("input", val);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
preferences,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped></style>
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-select
|
|
||||||
v-model="selected"
|
|
||||||
:items="households"
|
|
||||||
:label="label"
|
|
||||||
:hint="description"
|
|
||||||
:persistent-hint="!!description"
|
|
||||||
item-text="name"
|
|
||||||
:multiple="multiselect"
|
|
||||||
:prepend-inner-icon="$globals.icons.household"
|
|
||||||
return-object
|
|
||||||
>
|
|
||||||
<template #selection="data">
|
|
||||||
<v-chip
|
|
||||||
:key="data.index"
|
|
||||||
class="ma-1"
|
|
||||||
:input-value="data.selected"
|
|
||||||
small
|
|
||||||
close
|
|
||||||
label
|
|
||||||
color="accent"
|
|
||||||
dark
|
|
||||||
@click:close="removeByIndex(data.index)"
|
|
||||||
>
|
|
||||||
{{ data.item.name || data.item }}
|
|
||||||
</v-chip>
|
|
||||||
</template>
|
|
||||||
</v-select>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, onMounted, useContext } from "@nuxtjs/composition-api";
|
|
||||||
import { useHouseholdStore } from "~/composables/store/use-household-store";
|
|
||||||
|
|
||||||
interface HouseholdLike {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
value: {
|
|
||||||
type: Array as () => HouseholdLike[],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
multiselect: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, context) {
|
|
||||||
const selected = computed({
|
|
||||||
get: () => props.value,
|
|
||||||
set: (val) => {
|
|
||||||
context.emit("input", val);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (selected.value === undefined) {
|
|
||||||
selected.value = [];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const { i18n } = useContext();
|
|
||||||
const label = computed(
|
|
||||||
() => props.multiselect ? i18n.tc("household.households") : i18n.tc("household.household")
|
|
||||||
);
|
|
||||||
|
|
||||||
const { store: households } = useHouseholdStore();
|
|
||||||
function removeByIndex(index: number) {
|
|
||||||
if (selected.value === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newSelected = selected.value.filter((_, i) => i !== index);
|
|
||||||
selected.value = [...newSelected];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
selected,
|
|
||||||
label,
|
|
||||||
households,
|
|
||||||
removeByIndex,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user