Compare commits

...

261 commits

Author SHA1 Message Date
8bf1887d85 Merge branch 'main' into devel 2023-09-30 12:54:41 +02:00
bf15790153 lib/chiya/notes/note.ex aktualisiert 2023-09-30 12:54:13 +02:00
01a3c4f0da Merge pull request 'lib/chiya_web/controllers/page_html/note.html.heex aktualisiert' (#356) from devel into main
Reviewed-on: #356
2023-09-26 11:18:14 +02:00
0fac4df834 Merge branch 'main' into devel 2023-09-26 11:17:59 +02:00
fd580b112e assets/css/app.css aktualisiert 2023-09-26 11:17:51 +02:00
cdf15f5bf8 lib/chiya_web/controllers/page_html/note.html.heex aktualisiert 2023-09-26 11:16:30 +02:00
4135d72214 Merge pull request 'assets/css/app.css aktualisiert' (#355) from devel into main
Reviewed-on: #355
2023-09-26 10:51:13 +02:00
d2db822685 Merge branch 'main' into devel 2023-09-26 10:41:50 +02:00
bb086946a8 assets/css/app.css aktualisiert 2023-09-26 10:41:31 +02:00
cf3fa19044 Merge pull request 'devel' (#352) from devel into main
Reviewed-on: #352
2023-09-24 11:01:36 +02:00
94976b3414 Merge branch 'main' into devel 2023-09-24 11:00:33 +02:00
644cabd24f Merge remote-tracking branch 'origin/devel' into devel 2023-09-24 11:00:16 +02:00
2bb625af23 "improve" note_show_live 2023-09-24 11:00:02 +02:00
1b68c68644 add note_edit_live 2023-09-24 10:38:24 +02:00
c05259118f Merge pull request 'devel' (#340) from devel into main
Reviewed-on: #340
2023-09-23 22:49:48 +02:00
3127937528 Merge branch 'main' into devel 2023-09-23 22:49:42 +02:00
b6f060d1f8 improve header and footer 2023-09-23 22:43:17 +02:00
71d57cafd1 only show tags which do not have the tag yet 2023-09-23 22:43:06 +02:00
8079245269 Merge pull request 'devel' (#338) from devel into main
Reviewed-on: #338
2023-09-22 14:30:29 +02:00
26bcc7d383 Merge branch 'main' into devel 2023-09-22 14:30:20 +02:00
aa7f6ccebd assets/css/app.css aktualisiert 2023-09-22 14:30:06 +02:00
fe044ef5fd assets/css/app.css aktualisiert 2023-09-22 14:18:26 +02:00
45c35393e0 Merge pull request 'assets/css/app.css aktualisiert' (#337) from devel into main
Reviewed-on: #337
2023-09-22 13:58:33 +02:00
f3bcb578b9 Merge branch 'main' into devel 2023-09-22 13:21:50 +02:00
a8c39c25cf assets/css/app.css aktualisiert 2023-09-22 11:54:17 +02:00
55638d0212 Merge pull request 'assets/css/app.css aktualisiert' (#336) from devel into main
Reviewed-on: #336
2023-09-22 11:11:11 +02:00
7b07f932b8 Merge branch 'main' into devel 2023-09-22 11:10:37 +02:00
b6978c039b assets/css/app.css aktualisiert 2023-09-22 11:10:26 +02:00
a9faa2ff06 Merge pull request 'lib/chiya_web/components/layouts/root_public.html.heex aktualisiert' (#335) from devel into main
Reviewed-on: #335
2023-09-21 15:33:18 +02:00
1754c25227 Merge branch 'main' into devel 2023-09-21 15:33:13 +02:00
ad19ebf35a lib/chiya_web/components/layouts/root_public.html.heex aktualisiert 2023-09-21 15:32:59 +02:00
cc29ad9717 Merge pull request 'assets/css/app.css aktualisiert' (#334) from devel into main
Reviewed-on: #334
2023-09-21 15:32:27 +02:00
34893f4784 Merge branch 'main' into devel 2023-09-21 15:17:02 +02:00
f89d154200 assets/css/app.css aktualisiert 2023-09-21 15:16:52 +02:00
5756b62d66 Merge pull request 'assets/css/app.css aktualisiert' (#333) from devel into main
Reviewed-on: #333
2023-09-21 14:52:30 +02:00
6af681cc0e Merge branch 'main' into devel 2023-09-21 14:52:25 +02:00
62b6c83a0d assets/css/app.css aktualisiert 2023-09-21 14:52:18 +02:00
b17da2810c Merge pull request 'assets/css/app.css aktualisiert' (#332) from devel into main
Reviewed-on: #332
2023-09-21 13:32:18 +02:00
0f494d3140 Merge branch 'main' into devel 2023-09-21 13:32:13 +02:00
7fa78c2bef assets/css/app.css aktualisiert 2023-09-21 13:32:02 +02:00
a1cf8a0d32 Merge pull request 'devel' (#331) from devel into main
Reviewed-on: #331
2023-09-21 13:25:19 +02:00
54c0a05de2 Merge branch 'main' into devel 2023-09-21 13:24:50 +02:00
b34d92fee2 assets/css/app.css aktualisiert 2023-09-21 13:24:34 +02:00
f2a35e927f assets/css/app.css aktualisiert 2023-09-21 13:21:58 +02:00
369af1f2d6 assets/tailwind.config.js aktualisiert 2023-09-21 13:21:01 +02:00
c3050709f1 Merge pull request 'devel' (#330) from devel into main
Reviewed-on: #330
2023-09-21 13:19:16 +02:00
21107ad731 Merge branch 'main' into devel 2023-09-21 13:19:11 +02:00
7527d51cd9 assets/css/app.css aktualisiert 2023-09-21 13:19:05 +02:00
9e989b970e assets/css/app.css aktualisiert 2023-09-21 13:18:56 +02:00
dccd60b8ec Merge pull request 'assets/css/app.css aktualisiert' (#329) from devel into main
Reviewed-on: #329
2023-09-21 13:12:24 +02:00
27b252a1ca Merge branch 'main' into devel 2023-09-21 13:11:10 +02:00
216af3ad79 assets/css/app.css aktualisiert 2023-09-21 13:11:01 +02:00
0e60683149 Merge pull request 'assets/tailwind.config.js aktualisiert' (#328) from devel into main
Reviewed-on: #328
2023-09-21 13:06:51 +02:00
73c9a0caa5 Merge branch 'main' into devel 2023-09-21 13:06:39 +02:00
31756c0c6c assets/tailwind.config.js aktualisiert 2023-09-21 13:06:30 +02:00
0c37b7d429 Merge pull request 'assets/tailwind.config.js aktualisiert' (#327) from devel into main
Reviewed-on: #327
2023-09-20 14:59:20 +02:00
4a76d78cbc Merge branch 'main' into devel 2023-09-20 14:59:13 +02:00
07a29756e6 assets/tailwind.config.js aktualisiert 2023-09-20 14:58:52 +02:00
887463c07e Merge pull request 'devel' (#326) from devel into main
Reviewed-on: #326
2023-09-20 14:57:30 +02:00
0fc173c5ee Merge branch 'main' into devel 2023-09-20 14:56:00 +02:00
e0e927e851 assets/tailwind.config.js aktualisiert 2023-09-20 14:55:30 +02:00
cf827d22cb credo 2023-09-13 23:09:19 +02:00
4543c3b289 Merge pull request 'devel' (#322) from devel into main
Reviewed-on: #322
2023-09-13 21:00:45 +02:00
81cfbb8c5f Merge branch 'main' into devel 2023-09-13 21:00:38 +02:00
780a645fee autotagging 2023-09-13 20:59:15 +02:00
e17582823c hljs 2023-09-12 20:43:25 +02:00
695ba3f644 assets/css/hljs.css hinzugefügt 2023-09-12 13:24:16 +02:00
82483d14c5 Merge pull request 'devel' (#320) from devel into main
Reviewed-on: #320
2023-09-12 09:16:47 +02:00
2ece560b42 Merge branch 'main' into devel 2023-09-12 09:16:15 +02:00
611298e105 lib/chiya_web/controllers/tag_html/apply_prepare.html.heex aktualisiert 2023-09-12 09:15:57 +02:00
392bdf9ca3 lib/chiya_web/controllers/tag_html/apply_prepare.html.heex aktualisiert 2023-09-12 09:15:29 +02:00
3e43ab8841 Merge pull request 'filter form' (#319) from devel into main
Reviewed-on: #319
2023-09-12 07:21:05 +02:00
670ba48bbf Merge branch 'main' into devel 2023-09-12 07:20:58 +02:00
48c56b2739 filter form 2023-09-12 07:20:43 +02:00
25ad4dab5a Merge pull request 'devel' (#318) from devel into main
Reviewed-on: #318
2023-09-11 23:20:12 +02:00
d3aca71bf9 Merge branch 'main' into devel 2023-09-11 23:20:04 +02:00
513d18aa0b Merge remote-tracking branch 'origin/devel' into devel 2023-09-11 23:19:52 +02:00
abb9702028 bruh 2023-09-11 23:19:44 +02:00
c8e684c8a0 Merge pull request 'devel' (#317) from devel into main
Reviewed-on: #317
2023-09-11 20:17:32 +02:00
0a4b87a638 Merge branch 'main' into devel 2023-09-11 20:17:25 +02:00
7b808acd9d blub 2023-09-11 20:17:13 +02:00
cc255e0732 colors and stuff 2023-09-11 20:16:37 +02:00
f7a3730ea8 Merge pull request 'lib/chiya_web/components/public_components.ex aktualisiert' (#316) from devel into main
Reviewed-on: #316
2023-09-11 15:07:50 +02:00
89a8fcf3a7 Merge branch 'main' into devel 2023-09-11 15:07:40 +02:00
5e2b53812a lib/chiya_web/components/public_components.ex aktualisiert 2023-09-11 15:07:30 +02:00
2a7a9386b0 Merge pull request 'assets/css/app.css aktualisiert' (#315) from devel into main
Reviewed-on: #315
2023-09-11 14:46:53 +02:00
78c44c6709 Merge branch 'main' into devel 2023-09-11 14:46:16 +02:00
4379b54ad3 assets/css/app.css aktualisiert 2023-09-11 14:46:04 +02:00
27afd62703 Merge pull request 'devel' (#314) from devel into main
Reviewed-on: #314
2023-09-11 12:09:42 +02:00
bd35c168e1 Merge branch 'main' into devel 2023-09-11 12:09:35 +02:00
a129614b68 assets/css/app.css aktualisiert 2023-09-11 12:09:25 +02:00
f16b6ad9bc lib/chiya_web/controllers/page_html/note.html.heex aktualisiert 2023-09-11 12:08:53 +02:00
6e64926f0d lib/chiya_web/controllers/page_html/note.html.heex aktualisiert 2023-09-11 12:08:37 +02:00
ecf252bc22 Merge pull request 'devel' (#313) from devel into main
Reviewed-on: #313
2023-09-11 07:30:06 +02:00
2e33a1598a Merge branch 'main' into devel 2023-09-11 07:29:58 +02:00
ef73a7095e Merge remote-tracking branch 'origin/devel' into devel 2023-09-11 07:29:47 +02:00
5f69255289 move menu 2023-09-11 07:29:39 +02:00
155200ef60 Merge pull request 'devel' (#312) from devel into main
Reviewed-on: #312
2023-09-11 07:27:09 +02:00
96b688cfff Merge branch 'main' into devel 2023-09-11 07:27:04 +02:00
c895d22e73 Merge remote-tracking branch 'origin/devel' into devel 2023-09-11 07:26:51 +02:00
6e7f1216d7 fix 2023-09-11 07:26:44 +02:00
41a4da081b Merge pull request 'fix' (#311) from devel into main
Reviewed-on: #311
2023-09-11 07:24:13 +02:00
778be4a7c8 Merge branch 'main' into devel 2023-09-11 07:24:07 +02:00
1eb301b05d fix 2023-09-11 07:23:30 +02:00
8eb4337b0a Merge pull request 'devel' (#310) from devel into main
Reviewed-on: #310
2023-09-11 07:20:53 +02:00
a35674dbdc Merge branch 'main' into devel 2023-09-11 07:20:48 +02:00
7d07996329 fix tag-bar colors 2023-09-11 07:20:29 +02:00
7f12b81abf add tag-bar styles 2023-09-11 07:13:37 +02:00
838be53dec apply prose to page-header 2023-09-11 07:08:56 +02:00
75a7de2083 replace slate with neutral color 2023-09-11 07:07:49 +02:00
14ccc8b4ef Merge pull request 'tags' (#309) from devel into main
Reviewed-on: #309
2023-09-10 22:56:02 +02:00
2b6bb65bd1 Merge branch 'main' into devel 2023-09-10 22:55:53 +02:00
4905e38615 tags 2023-09-10 22:55:16 +02:00
97eea73676 Merge pull request 'remove kbar, add flop, clean up' (#308) from devel into main
Reviewed-on: #308
2023-09-10 10:58:39 +02:00
949bffcf48 Merge branch 'main' into devel 2023-09-10 10:58:34 +02:00
1331b69013 remove kbar, add flop, clean up 2023-09-10 10:58:08 +02:00
f548c24487 Merge pull request 'devel' (#307) from devel into main
Reviewed-on: #307
2023-09-09 23:47:31 +02:00
0b43c3916c Merge branch 'main' into devel 2023-09-09 23:47:24 +02:00
106f86521c generate excerpt from <!-- more --> placeholder 2023-09-09 23:46:59 +02:00
ad0d130fc8 improve outline rendering 2023-09-09 23:46:37 +02:00
4813936ff4 Merge pull request 'notes linking here' (#306) from devel into main
Reviewed-on: #306
2023-09-09 16:18:32 +02:00
11467a2d6f Merge branch 'main' into devel 2023-09-09 16:18:24 +02:00
8775a9d825 notes linking here 2023-09-09 16:17:53 +02:00
d9d2168639 Merge pull request 'devel' (#305) from devel into main
Reviewed-on: #305
2023-09-09 16:10:28 +02:00
55ac50b43a Merge branch 'main' into devel 2023-09-09 16:10:21 +02:00
a0a3a6b479 Merge remote-tracking branch 'origin/devel' into devel 2023-09-09 16:08:38 +02:00
58f5bd364c burst of energy 2023-09-09 16:08:30 +02:00
4f05d2b3dd Merge pull request 'devel' (#304) from devel into main
Reviewed-on: #304
2023-09-09 15:12:05 +02:00
24074b0015 Merge branch 'main' into devel 2023-09-09 15:11:56 +02:00
d822f6ecb2 Merge remote-tracking branch 'origin/devel' into devel 2023-09-09 15:06:25 +02:00
89ed7fced0 tags 2023-09-09 15:06:16 +02:00
a160d402e1 Merge pull request 'devel' (#303) from devel into main
Reviewed-on: #303
2023-09-09 14:37:57 +02:00
02cb5c3e7a Merge branch 'main' into devel 2023-09-09 14:37:51 +02:00
9ac8cedee2 Merge remote-tracking branch 'origin/devel' into devel 2023-09-09 14:37:32 +02:00
fa495b68e9 yeah 2023-09-09 14:37:26 +02:00
42c85eec91 Merge pull request 'ko' (#302) from devel into main
Reviewed-on: #302
2023-09-09 14:17:25 +02:00
396f882c6d Merge branch 'main' into devel 2023-09-09 14:17:19 +02:00
347b37b3be ko 2023-09-09 14:16:58 +02:00
c468c7a7c1 Merge pull request 'devel' (#301) from devel into main
Reviewed-on: #301
2023-09-09 14:15:27 +02:00
60afdf9d15 Merge branch 'main' into devel 2023-09-09 14:15:18 +02:00
6e04014c5e Merge remote-tracking branch 'origin/devel' into devel 2023-09-09 14:14:55 +02:00
3a0c0764c5 kop 2023-09-09 14:14:37 +02:00
5a78c61e0c Merge pull request 'devel' (#300) from devel into main
Reviewed-on: #300
2023-09-09 14:12:34 +02:00
c60ed1290c Merge branch 'main' into devel 2023-09-09 14:12:20 +02:00
d197560b57 Merge remote-tracking branch 'origin/devel' into devel 2023-09-09 14:12:05 +02:00
01b007f915 ko 2023-09-09 14:11:55 +02:00
4612c9d91b Merge pull request 'devl' (#299) from devel into main
Reviewed-on: #299
2023-09-09 14:10:10 +02:00
7ba85e9e12 Merge branch 'main' into devel 2023-09-09 14:10:04 +02:00
f3b2a92e66 Merge remote-tracking branch 'origin/devel' into devel 2023-09-09 14:09:36 +02:00
12e36d616f bla 2023-09-09 14:09:28 +02:00
62d1502901 Merge pull request 'fix styles' (#298) from devel into main
Reviewed-on: #298
2023-09-09 13:56:33 +02:00
0b6ce9685f Merge branch 'main' into devel 2023-09-09 13:56:27 +02:00
cd17769668 fix styles 2023-09-09 13:56:11 +02:00
60f27620da Merge pull request 'devel' (#297) from devel into main
Reviewed-on: #297
2023-09-09 13:49:39 +02:00
087f0f35f8 Merge branch 'main' into devel 2023-09-09 13:49:32 +02:00
82dfecd0ad Merge remote-tracking branch 'origin/devel' into devel 2023-09-09 13:49:17 +02:00
0d47ecce00 rip and tear 2023-09-09 13:49:08 +02:00
01846f6b8f Merge pull request 'devel' (#296) from devel into main
Reviewed-on: #296
2023-09-09 13:15:01 +02:00
c8a7d1943f Merge branch 'main' into devel 2023-09-09 13:14:56 +02:00
cd3b1dae94 Merge remote-tracking branch 'origin/devel' into devel 2023-09-09 13:14:15 +02:00
a4feb8bba5 react 2023-09-09 13:14:07 +02:00
881679d338 Merge pull request 'lol' (#294) from devel into main
Reviewed-on: #294
2023-09-09 13:06:53 +02:00
fce64036a2 Merge branch 'main' into devel 2023-09-09 13:06:47 +02:00
4c4daf9e28 lol 2023-09-09 13:06:27 +02:00
0c8e62e770 Merge pull request 'devel' (#293) from devel into main
Reviewed-on: #293
2023-09-09 12:55:10 +02:00
8f8ed7568b color 2023-09-09 12:54:47 +02:00
0f83288d63 add nesting 2023-09-09 12:54:42 +02:00
0e10385ee6 Merge pull request 'fix' (#292) from devel into main
Reviewed-on: #292
2023-09-09 12:47:47 +02:00
e4bd324be4 fix 2023-09-09 12:47:25 +02:00
02f9c4f4c4 Merge pull request 'devel' (#291) from devel into main
Reviewed-on: #291
2023-09-09 10:59:23 +02:00
2833407c21 Merge branch 'main' into devel 2023-09-09 10:59:19 +02:00
50266b3052 Merge remote-tracking branch 'origin/devel' into devel 2023-09-09 10:58:53 +02:00
a8e3e4acb5 fix 2023-09-09 10:58:44 +02:00
9fafc54df5 Merge pull request 'yay' (#290) from devel into main
Reviewed-on: #290
2023-09-09 10:25:25 +02:00
3da13d2eb1 Merge branch 'main' into devel 2023-09-09 10:24:54 +02:00
c595b20668 yay 2023-09-09 10:24:22 +02:00
3377daadda Merge pull request 'devel' (#289) from devel into main
Reviewed-on: #289
2023-09-08 23:08:05 +02:00
dcf1cb96cf Merge branch 'main' into devel 2023-09-08 23:07:52 +02:00
b33a9f786d fix slfdsivj 2023-09-08 23:01:39 +02:00
38f1a61ac8 Merge pull request 'lib/chiya_web/controllers/page_html/note_list_microblog.html.heex aktualisiert' (#288) from devel into main
Reviewed-on: #288
2023-09-08 14:37:28 +02:00
aed924f2a8 Merge branch 'main' into devel 2023-09-08 14:37:22 +02:00
f5bc59d5f1 lib/chiya_web/controllers/page_html/note_list_microblog.html.heex aktualisiert 2023-09-08 14:36:59 +02:00
0d0bfc3913 Merge pull request 'devel' (#287) from devel into main
Reviewed-on: #287
2023-09-08 13:32:17 +02:00
813b63acd5 Merge branch 'main' into devel 2023-09-08 13:32:10 +02:00
552046f802 assets/css/app.css aktualisiert 2023-09-08 13:25:48 +02:00
3184796829 lib/chiya_web/components/layouts/root_public.html.heex aktualisiert 2023-09-08 13:24:30 +02:00
128f876c48 lib/chiya_web/controllers/page_html/note.html.heex aktualisiert 2023-09-08 13:21:16 +02:00
98adac2114 Merge pull request 'lib/chiya_web/controllers/page_html/note.html.heex aktualisiert' (#286) from devel into main
Reviewed-on: #286
2023-09-08 13:18:46 +02:00
a38047b0f7 Merge branch 'main' into devel 2023-09-08 13:18:39 +02:00
33f4ff56f3 lib/chiya_web/controllers/page_html/note.html.heex aktualisiert 2023-09-08 13:18:29 +02:00
bc4afebb3c Merge pull request 'lib/chiya_web/controllers/page_html/note.html.heex aktualisiert' (#285) from devel into main
Reviewed-on: #285
2023-09-08 13:01:41 +02:00
4eb76f679f Merge branch 'main' into devel 2023-09-08 13:01:35 +02:00
17382a449e lib/chiya_web/controllers/page_html/note.html.heex aktualisiert 2023-09-08 13:01:26 +02:00
07fd0849a5 Merge pull request 'devel' (#284) from devel into main
Reviewed-on: #284
2023-09-08 12:46:44 +02:00
ca4d2ca05a Merge branch 'main' into devel 2023-09-08 12:46:35 +02:00
50ea3397f5 lib/chiya_web/controllers/page_html.ex aktualisiert 2023-09-08 12:46:19 +02:00
8d33ce80e8 lib/chiya_web/controllers/page_html.ex aktualisiert 2023-09-08 12:44:57 +02:00
a6c0224688 Merge pull request 'devel' (#283) from devel into main
Reviewed-on: #283
2023-09-08 12:41:04 +02:00
4bfb8bbd8f Merge branch 'main' into devel 2023-09-08 12:40:56 +02:00
5f7be6aca1 lib/chiya_web/controllers/page_html/bookmarks.html.heex aktualisiert 2023-09-08 12:40:46 +02:00
e863b6f18b lib/chiya_web/controllers/page_html/wiki.html.heex aktualisiert 2023-09-08 12:40:18 +02:00
c412fb48bc Merge pull request 'assets/css/app.css aktualisiert' (#282) from devel into main
Reviewed-on: #282
2023-09-08 12:06:27 +02:00
25f4d2624e Merge branch 'main' into devel 2023-09-08 12:06:22 +02:00
f768983ece assets/css/app.css aktualisiert 2023-09-08 12:05:05 +02:00
487cae070c Merge pull request 'redesign' (#281) from devel into main
Reviewed-on: #281
2023-09-05 22:02:37 +02:00
ee33075fc1 Merge branch 'main' into devel 2023-09-05 22:02:28 +02:00
6aad7bb89b redesign 2023-09-05 21:59:37 +02:00
34c8a217e9 Merge pull request 'assets/css/app.css aktualisiert' (#277) from devel into main
Reviewed-on: #277
2023-08-24 09:13:02 +02:00
80aaefbd87 Merge branch 'main' into devel 2023-08-24 09:12:56 +02:00
f2956e31a2 assets/css/app.css aktualisiert 2023-08-24 09:12:42 +02:00
a4bc1536eb Merge pull request 'devel' (#275) from devel into main
Reviewed-on: #275
2023-08-24 09:08:07 +02:00
cdff97f5c0 Merge branch 'main' into devel 2023-08-24 09:08:00 +02:00
85ebf546df assets/css/app.css aktualisiert 2023-08-24 09:07:50 +02:00
e37378ac06 lib/chiya_web/components/public_components.ex aktualisiert 2023-08-24 08:51:00 +02:00
51a6b3dece Merge pull request 'devel' (#274) from devel into main
Reviewed-on: #274
2023-08-23 16:18:07 +02:00
a327c1a635 Merge branch 'main' into devel 2023-08-23 16:17:59 +02:00
680d474f45 lib/chiya_web/components/public_components.ex aktualisiert 2023-08-23 16:15:50 +02:00
c4a88d2e34 lib/chiya_web/components/public_components.ex aktualisiert 2023-08-23 16:15:36 +02:00
1d1a47888b assets/css/app.css aktualisiert 2023-08-23 16:14:11 +02:00
db2b49a85d lib/chiya_web/controllers/page_html/note.html.heex aktualisiert 2023-08-23 16:12:22 +02:00
ec6510537d Merge pull request 'lib/chiya_web/components/public_components.ex aktualisiert' (#273) from devel into main
Reviewed-on: #273
2023-08-23 15:06:02 +02:00
673941e39d Merge branch 'main' into devel 2023-08-23 15:05:55 +02:00
8b1b9378dc lib/chiya_web/components/public_components.ex aktualisiert 2023-08-23 15:05:03 +02:00
663be7e442 Merge pull request 'lib/chiya_web/components/public_components.ex aktualisiert' (#272) from devel into main
Reviewed-on: #272
2023-08-23 14:05:30 +02:00
b684518599 Merge branch 'main' into devel 2023-08-23 14:04:48 +02:00
90c174daea lib/chiya_web/components/public_components.ex aktualisiert 2023-08-23 14:04:36 +02:00
724952c740 Merge pull request 'fix fetching slug' (#271) from devel into main
Reviewed-on: #271
2023-08-22 00:08:18 +02:00
16e8aee42b Merge branch 'main' into devel 2023-08-22 00:07:47 +02:00
b901daa542 fix fetching slug 2023-08-22 00:07:23 +02:00
3c5731b657 Merge pull request 'more logging' (#270) from devel into main
Reviewed-on: #270
2023-08-22 00:02:40 +02:00
12f78d4e19 Merge branch 'main' into devel 2023-08-22 00:02:26 +02:00
e228633eb3 more logging 2023-08-22 00:02:09 +02:00
8ee52c33c5 Merge pull request 'devel' (#269) from devel into main
Reviewed-on: #269
2023-08-21 23:55:14 +02:00
e62d95a842 Merge branch 'main' into devel 2023-08-21 23:55:07 +02:00
e48242feb2 more logging 2023-08-21 23:54:33 +02:00
0012d8a5f7 print debug in prod 2023-08-21 20:53:25 +02:00
ecaba6467a Merge pull request 'devel' (#268) from devel into main
Reviewed-on: #268
2023-08-21 20:50:59 +02:00
d2a9268947 Merge branch 'main' into devel 2023-08-21 20:50:46 +02:00
33d1120832 Merge remote-tracking branch 'origin/devel' into devel 2023-08-21 20:50:21 +02:00
c3c84660e9 update plug_micropub 2023-08-21 20:50:15 +02:00
6cd24ecb9a Merge pull request 'devel' (#267) from devel into main
Reviewed-on: #267
2023-08-21 19:55:27 +02:00
d60172ee03 Merge branch 'main' into devel 2023-08-21 19:55:21 +02:00
913786d620 Merge remote-tracking branch 'origin/devel' into devel 2023-08-21 19:54:38 +02:00
78631b2e04 update logging for micropub 2023-08-21 19:54:30 +02:00
16f4221c6c Merge pull request 'devel' (#266) from devel into main
Reviewed-on: #266
2023-08-21 19:43:12 +02:00
b1e1b2bd00 Merge branch 'main' into devel 2023-08-21 19:43:04 +02:00
e52ae4af22 Merge remote-tracking branch 'origin/devel' into devel 2023-08-21 19:42:24 +02:00
b59449de26 integrate plug_micropub 2023-08-21 19:42:16 +02:00
383841c4b5 Merge pull request 'try to fix update' (#265) from devel into main
Reviewed-on: #265
2023-08-21 07:15:31 +02:00
60ec24bbd9 Merge branch 'main' into devel 2023-08-21 07:15:24 +02:00
9502748dc9 try to fix update 2023-08-20 23:47:00 +02:00
cdad0a94c9 Merge pull request 'devel' (#264) from devel into main
Reviewed-on: #264
2023-08-16 22:55:36 +02:00
4d1d5e72be Merge branch 'main' into devel 2023-08-16 22:55:25 +02:00
0267ae07d8 add like and repost as supported types 2023-08-16 22:54:57 +02:00
f1c333acec fix tags 2023-08-16 22:46:13 +02:00
a09dd582e5 fix bookmarks 2023-08-16 22:39:46 +02:00
dd88829544 update npm deps 2023-08-16 22:39:35 +02:00
973de29668 Merge remote-tracking branch 'origin/devel' into devel 2023-08-16 22:34:15 +02:00
3ad472b5c4 fix filtering 2023-08-16 22:34:06 +02:00
1fcae00b12 Merge pull request 'Update dependency gettext to ~> 0.23' (#260) from renovate/gettext-0.x into devel
Reviewed-on: #260
2023-08-16 22:33:48 +02:00
c26bf4cc46 Merge pull request 'wip of source query' (#262) from devel into main
Reviewed-on: #262
2023-08-14 18:54:23 +02:00
69 changed files with 2761 additions and 1646 deletions

216
.credo.exs Normal file
View file

@ -0,0 +1,216 @@
# This file contains the configuration for Credo and you are probably reading
# this after creating it with `mix credo.gen.config`.
#
# If you find anything wrong or unclear in this file, please report an
# issue on GitHub: https://github.com/rrrene/credo/issues
#
%{
#
# You can have as many configs as you like in the `configs:` field.
configs: [
%{
#
# Run any config using `mix credo -C <name>`. If no config name is given
# "default" is used.
#
name: "default",
#
# These are the files included in the analysis:
files: %{
#
# You can give explicit globs or simply directories.
# In the latter case `**/*.{ex,exs}` will be used.
#
included: [
"lib/",
"src/",
"test/",
"web/",
"apps/*/lib/",
"apps/*/src/",
"apps/*/test/",
"apps/*/web/"
],
excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"]
},
#
# Load and configure plugins here:
#
plugins: [],
#
# If you create your own checks, you must specify the source files for
# them here, so they can be loaded by Credo before running the analysis.
#
requires: [],
#
# If you want to enforce a style guide and need a more traditional linting
# experience, you can change `strict` to `true` below:
#
strict: false,
#
# To modify the timeout for parsing files, change this value:
#
parse_timeout: 5000,
#
# If you want to use uncolored output by default, you can change `color`
# to `false` below:
#
color: false,
#
# You can customize the parameters of any check by adding a second element
# to the tuple.
#
# To disable a check put `false` as second element:
#
# {Credo.Check.Design.DuplicatedCode, false}
#
checks: %{
enabled: [
#
## Consistency Checks
#
{Credo.Check.Consistency.ExceptionNames, []},
{Credo.Check.Consistency.LineEndings, []},
{Credo.Check.Consistency.ParameterPatternMatching, []},
{Credo.Check.Consistency.SpaceAroundOperators, []},
{Credo.Check.Consistency.SpaceInParentheses, []},
{Credo.Check.Consistency.TabsOrSpaces, []},
#
## Design Checks
#
# You can customize the priority of any check
# Priority values are: `low, normal, high, higher`
#
{Credo.Check.Design.AliasUsage,
[priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]},
# You can also customize the exit_status of each check.
# If you don't want TODO comments to cause `mix credo` to fail, just
# set this value to 0 (zero).
#
{Credo.Check.Design.TagTODO, [exit_status: 2]},
{Credo.Check.Design.TagFIXME, []},
#
## Readability Checks
#
{Credo.Check.Readability.AliasOrder, []},
{Credo.Check.Readability.FunctionNames, []},
{Credo.Check.Readability.LargeNumbers, []},
{Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]},
{Credo.Check.Readability.ModuleAttributeNames, []},
{Credo.Check.Readability.ModuleDoc, []},
{Credo.Check.Readability.ModuleNames, []},
{Credo.Check.Readability.ParenthesesInCondition, []},
{Credo.Check.Readability.ParenthesesOnZeroArityDefs, []},
{Credo.Check.Readability.PipeIntoAnonymousFunctions, []},
{Credo.Check.Readability.PredicateFunctionNames, []},
{Credo.Check.Readability.PreferImplicitTry, []},
{Credo.Check.Readability.RedundantBlankLines, []},
{Credo.Check.Readability.Semicolons, []},
{Credo.Check.Readability.SpaceAfterCommas, []},
{Credo.Check.Readability.StringSigils, []},
{Credo.Check.Readability.TrailingBlankLine, []},
{Credo.Check.Readability.TrailingWhiteSpace, []},
{Credo.Check.Readability.UnnecessaryAliasExpansion, []},
{Credo.Check.Readability.VariableNames, []},
{Credo.Check.Readability.WithSingleClause, []},
#
## Refactoring Opportunities
#
{Credo.Check.Refactor.Apply, []},
{Credo.Check.Refactor.CondStatements, []},
{Credo.Check.Refactor.CyclomaticComplexity, []},
{Credo.Check.Refactor.FunctionArity, []},
{Credo.Check.Refactor.LongQuoteBlocks, []},
{Credo.Check.Refactor.MatchInCondition, []},
{Credo.Check.Refactor.MapJoin, []},
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
{Credo.Check.Refactor.Nesting, []},
{Credo.Check.Refactor.UnlessWithElse, []},
{Credo.Check.Refactor.WithClauses, []},
{Credo.Check.Refactor.FilterCount, []},
{Credo.Check.Refactor.FilterFilter, []},
{Credo.Check.Refactor.RejectReject, []},
{Credo.Check.Refactor.RedundantWithClauseResult, []},
#
## Warnings
#
{Credo.Check.Warning.ApplicationConfigInModuleAttribute, []},
{Credo.Check.Warning.BoolOperationOnSameValues, []},
{Credo.Check.Warning.Dbg, []},
{Credo.Check.Warning.ExpensiveEmptyEnumCheck, []},
{Credo.Check.Warning.IExPry, []},
{Credo.Check.Warning.IoInspect, []},
{Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []},
{Credo.Check.Warning.OperationOnSameValues, []},
{Credo.Check.Warning.OperationWithConstantResult, []},
{Credo.Check.Warning.RaiseInsideRescue, []},
{Credo.Check.Warning.SpecWithStruct, []},
{Credo.Check.Warning.WrongTestFileExtension, []},
{Credo.Check.Warning.UnusedEnumOperation, []},
{Credo.Check.Warning.UnusedFileOperation, []},
{Credo.Check.Warning.UnusedKeywordOperation, []},
{Credo.Check.Warning.UnusedListOperation, []},
{Credo.Check.Warning.UnusedPathOperation, []},
{Credo.Check.Warning.UnusedRegexOperation, []},
{Credo.Check.Warning.UnusedStringOperation, []},
{Credo.Check.Warning.UnusedTupleOperation, []},
{Credo.Check.Warning.UnsafeExec, []}
],
disabled: [
#
# Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`)
#
# Controversial and experimental checks (opt-in, just move the check to `:enabled`
# and be sure to use `mix credo --strict` to see low priority checks)
#
{Credo.Check.Consistency.MultiAliasImportRequireUse, []},
{Credo.Check.Consistency.UnusedVariableNames, []},
{Credo.Check.Design.DuplicatedCode, []},
{Credo.Check.Design.SkipTestWithoutComment, []},
{Credo.Check.Readability.AliasAs, []},
{Credo.Check.Readability.BlockPipe, []},
{Credo.Check.Readability.ImplTrue, []},
{Credo.Check.Readability.MultiAlias, []},
{Credo.Check.Readability.NestedFunctionCalls, []},
{Credo.Check.Readability.OneArityFunctionInPipe, []},
{Credo.Check.Readability.SeparateAliasRequire, []},
{Credo.Check.Readability.SingleFunctionToBlockPipe, []},
{Credo.Check.Readability.SinglePipe, []},
{Credo.Check.Readability.Specs, []},
{Credo.Check.Readability.StrictModuleLayout, []},
{Credo.Check.Readability.WithCustomTaggedTuple, []},
{Credo.Check.Readability.OnePipePerLine, []},
{Credo.Check.Refactor.ABCSize, []},
{Credo.Check.Refactor.AppendSingleItem, []},
{Credo.Check.Refactor.DoubleBooleanNegation, []},
{Credo.Check.Refactor.FilterReject, []},
{Credo.Check.Refactor.IoPuts, []},
{Credo.Check.Refactor.MapMap, []},
{Credo.Check.Refactor.ModuleDependencies, []},
{Credo.Check.Refactor.NegatedIsNil, []},
{Credo.Check.Refactor.PassAsyncInTestCases, []},
{Credo.Check.Refactor.PipeChainStart, []},
{Credo.Check.Refactor.RejectFilter, []},
{Credo.Check.Refactor.VariableRebinding, []},
{Credo.Check.Warning.LazyLogging, []},
{Credo.Check.Warning.LeakyEnvironment, []},
{Credo.Check.Warning.MapGetUnsafePass, []},
{Credo.Check.Warning.MixEnv, []},
{Credo.Check.Warning.UnsafeToAtom, []}
# {Credo.Check.Refactor.MapInto, []},
#
# Custom checks can be created using `mix credo.gen.check`.
#
]
}
}
]
}

View file

@ -14,10 +14,14 @@ const plugins = [
]
let opts = {
entryPoints: ['js/app.js', 'js/public.js'],
entryPoints: [
'js/app.js',
'js/public.js'
],
bundle: true,
target: 'es2017',
target: 'es2016',
outdir: '../priv/static/assets',
external: ["*.css", "fonts/*", "images/*"],
logLevel: 'info',
loader,
plugins

View file

@ -2,145 +2,247 @@
@import "tailwindcss/components";
@import "tailwindcss/utilities";
@import "./reset.css";
@import "./gruvbox.css";
@import "./hljs.css";
@import "./lightbox.css";
@import "./tablesort.css";
/*
The base layer is for things like reset rules or default styles applied to plain HTML elements.
*/
@layer base {
:root {
--font-features: "case", "cpsp", "frac", "salt", "ss02", "ccmp";
}
:root {
font-family: 'Inter', sans-serif;
font-feature-settings: var(--font-features);
}
@supports (font-variation-settings: normal) {
:root {
font-family: 'Inter var', sans-serif;
font-feature-settings: var(--font-features);
}
}
:root {
--color-primary: 214 93 14; /* orange */
--color-primary1: 175 58 3; /* orange faded */
--color-secondary: 104 157 106; /* aqua */
--color-secondary1: 66 123 88; /* aqua faded */
--color-blue: 69 133 136 ; /* blue */
--color-blue1: 7 102 120; /* blue faded */
--color-purple: 177 98 134 ; /* purple */
--color-purple1: 143 63 113; /* purple faded */
--color-yellow: 215 153 33; /* yellow */
--color-yellow1: 181 118 20; /* yellow faded */
--color-background: 253 244 193; /* light0 */
--color-background1: 235 219 178; /* light1 */
--color-foreground: 60 56 54; /* dark1 */
--color-foreground1: 80 73 69; /* dark2 */
--color-heading: var(--color-secondary);
@apply selection:bg-theme-primary/50;
--color-foreground: 15 23 42;
--color-background: 241 245 249;
}
:root[data-mode=dark] {
--color-primary: 214 93 14; /* orange */
--color-primary1: 254 128 25; /* orange bright */
--color-foreground: 241 245 249;
--color-background: 15 23 42;
}
--color-secondary: 104 157 106; /* aqua */
--color-secondary1: 142 192 124; /* aqua bright */
html {
font-family: 'Inter', sans-serif;
font-feature-settings: "case", "cpsp", "frac", "salt", "ccmp", "cv01", "cv02", "cv03", "cv04", "cv05", "cv06", "cv07", "cv09", "cv10", "cv11";
}
--color-blue: 69 133 136 ; /* blue */
--color-blue1: 131 165 152; /* blue bright */
body#site-body {
@apply bg-gradient-to-br from-background from-50% to-rose-100 dark:to-rose-950 text-foreground;
}
--color-purple: 177 98 134 ; /* purple */
--color-purple1: 250 189 47; /* purple bright */
.stack > * + * {
margin-block-start: var(--flow-space, 1em);
}
--color-yellow: 215 153 33; /* yellow */
--color-yellow1: 181 118 20; /* yellow bright */
--color-background: 40 40 40; /* dark0 */
--color-background1: 60 56 54; /* dark1 */
--color-foreground: 235 219 178; /* light1 */
--color-foreground1: 213 196 161; /* light2 */
--color-heading: var(--color-secondary);
:root .prose {
@apply prose-inhji;
}
:root[data-mode=dark] .prose {
@apply prose-invert;
}
.prose a, a.underline-link {
@apply underline decoration-1 decoration-theme-primary hover:decoration-theme-base transition;
/* https://stackoverflow.com/questions/5379752/css-style-external-links */
a[href]:not(:where(
/* exclude hash only links */
[href^="#"],
/* exclude relative but not double slash only links */
[href^="/"]:not([href^="//"]),
/* domains to exclude */
[href*="//inhji.de"],
/* subdomains to exclude */
[href*="//cloud.inhji.de"],
)):after {
content: '↬';
}
.prose a[href^="http://"], .prose a[href^="https://"] {
@apply after:content-['_↗']
/*
* ============= SITE LAYOUT =============
*/
/* === SITE HEADER === */
#site-header {
@apply sticky top-0 block px-3 py-6 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 text-white print:hidden;
& nav ul {
@apply flex;
}
& a {
@apply p-3 rounded transition border border-transparent hover:border-white;
}
& #site-title {
@apply font-bold uppercase;
}
}
.prose em {
@apply text-theme-quaternary;
/* === PRIMARY SIDEBAR === */
#primary-sidebar {
@apply col-span-1;
@apply prose-a:no-underline;
& nav {
@apply flex justify-between md:flex-row lg:flex-col mb-6 lg:mb-0;
}
& h3 {
@apply m-0;
}
& a.active {
@apply text-primary-500;
}
}
.prose h1, h2, h3, h4 {
@apply before:font-light before:text-theme-base/25;
/* === SITE CONTENT === */
#site-content {
@apply grid grid-cols-1 lg:grid-cols-5 gap-0 lg:gap-12;
@apply mt-6 sm:mt-12 px-6 sm:px-0;
#content-wrapper {
@apply col-span-4;
}
#secondary-sidebar {
@apply col-span-1;
}
}
.prose h1 {
@apply before:content-['#'];
/* === SITE FOOTER === */
#site-footer {
@apply p-8 mt-8 max-w-none bg-foreground/20 border-t border-foreground/10;
}
.prose h2 {
@apply before:content-['##'];
/* === CONCERNING MULTIPLE LAYOUT BLOCKS === */
#site-content, #site-footer, #primary-sidebar, #secondary-sidebar {
@apply hover:prose-a:text-primary-600 hover:prose-a:transition;
}
.prose h3 {
@apply before:content-['###'];
/*
* ============= PAGE LAYOUT =============
*/
/* === PAGE HEADER === */
header.page-header {
@apply border-b border-foreground/50 mb-6 prose max-w-none;
}
.prose h4 {
@apply before:content-['####'];
header.page-header h1 {
@apply text-3xl leading-loose font-bold;
}
.prose pre {
@apply p-0;
header.page-header p {
@apply mb-3;
}
.prose img {
@apply rounded-lg;
/* === PAGE GRID === */
section.page-grid {
@apply grid grid-cols-1 md:grid-cols-3 gap-6;
> section {
@apply col-span-1 md:col-span-2 last:md:col-span-3;
}
> aside {
@apply print:hidden;
}
}
}
/*
The components layer is for class-based styles that you want to be able to override with utilities.
*/
@layer components {
/* Set width and color for identity icons */
a[rel] svg {
width: 1em;
fill: rgb(var(--color-foreground));
}
.alert {
@apply p-3 mt-3 rounded;
.note {
.note-link {
@apply text-center text-neutral-900 dark:text-neutral-100;
}
}
.alert.alert-danger {
@apply bg-red-100 text-red-500 dark:bg-red-950 dark:text-red-500;
.divider {
@apply flex text-primary-500 items-center my-8 w-full mx-auto last:hidden;
@apply before:flex-1 before:content-[''] before:bg-neutral-500/25 before:p-[0.5px] before:mr-2;
@apply after:flex-1 after:content-[''] after:bg-neutral-500/25 after:p-[0.5px] after:ml-2;
}
.footnotes li p { display: inline; }
.footnotes hr { display: none; }
.footnote:before { content: '{'; }
.footnote:after { content: '}'; }
.dot {
@apply before:content-['·']
}
a.button {
@apply inline-block text-theme-base px-3 py-2.5 border border-theme-background1 hover:bg-theme-background1 rounded transition font-semibold;
.tag-bar {
& .letter {
@apply text-neutral-900 dark:text-neutral-100 capitalize border rounded text-sm px-2 py-1 inline-block mb-2;
}
& li {
@apply mb-2;
}
& a {
@apply text-neutral-900 dark:text-neutral-100 border rounded text-sm px-2 py-1 inline-block mb-2 transition;
}
& .count {
@apply text-sm font-mono rounded-full py-0.5 px-1 align-middle;
}
}
.pagination {
@apply flex justify-between mt-6;
& .pagination-previous, & .pagination-next {
@apply block py-2 px-3 bg-neutral-700 text-white rounded;
}
& .pagination-previous.disabled, & .pagination-next.disabled {
@apply bg-neutral-500;
}
& .pagination-list {
@apply hidden;
}
}
/* === CARD LIST (ADMIN) === */
.card-list {
@apply flex flex-col gap-3 mt-6;
}
/* === CARD (ADMIN) === */
.card {
@apply bg-neutral-100 dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100 p-3 rounded;
& header {
& h2 {
@apply text-xl leading-normal;
}
}
& footer {
@apply flex gap-3 text-sm;
}
}
}
/*
The utilities layer is for small, single-purpose classes that should always take precedence over any other styles.
*/
@layer utilities {
.hljs {
background: transparent !important;
}
a.invalid {
@apply line-through;
}
}

148
assets/css/app.old.css Normal file
View file

@ -0,0 +1,148 @@
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
@import "./reset.css";
@import "./gruvbox.css";
@import "./lightbox.css";
@import "./tablesort.css";
@layer base {
:root {
--font-features: "case", "cpsp", "frac", "salt", "ccmp", "cv01", "cv02", "cv03", "cv04", "cv05", "cv06", "cv07", "cv09", "cv10", "cv11";
}
:root {
font-family: 'Inter', sans-serif;
font-feature-settings: var(--font-features);
}
@supports (font-variation-settings: normal) {
:root {
font-family: 'Inter var', sans-serif;
font-feature-settings: var(--font-features);
}
}
:root {
--color-primary: 214 93 14; /* orange */
--color-primary1: 175 58 3; /* orange faded */
--color-secondary: 104 157 106; /* aqua */
--color-secondary1: 66 123 88; /* aqua faded */
--color-blue: 69 133 136 ; /* blue */
--color-blue1: 7 102 120; /* blue faded */
--color-purple: 177 98 134 ; /* purple */
--color-purple1: 143 63 113; /* purple faded */
--color-yellow: 215 153 33; /* yellow */
--color-yellow1: 181 118 20; /* yellow faded */
--color-background: 253 244 193; /* light0 */
--color-background1: 235 219 178; /* light1 */
--color-foreground: 60 56 54; /* dark1 */
--color-foreground1: 80 73 69; /* dark2 */
--color-heading: var(--color-secondary);
@apply selection:bg-theme-primary/50;
}
:root[data-mode=dark] {
--color-primary: 214 93 14; /* orange */
--color-primary1: 254 128 25; /* orange bright */
--color-secondary: 104 157 106; /* aqua */
--color-secondary1: 142 192 124; /* aqua bright */
--color-blue: 69 133 136 ; /* blue */
--color-blue1: 131 165 152; /* blue bright */
--color-purple: 177 98 134 ; /* purple */
--color-purple1: 250 189 47; /* purple bright */
--color-yellow: 215 153 33; /* yellow */
--color-yellow1: 181 118 20; /* yellow bright */
--color-background: 40 40 40; /* dark0 */
--color-background1: 60 56 54; /* dark1 */
--color-foreground: 235 219 178; /* light1 */
--color-foreground1: 213 196 161; /* light2 */
--color-heading: var(--color-secondary);
}
:root[data-mode=dark] .prose {
@apply prose-invert;
}
.prose a, a.underline-link {
@apply underline decoration-1 decoration-theme-primary hover:decoration-theme-base transition;
}
.prose a[href^="http://"], .prose a[href^="https://"] {
@apply after:content-['_↗']
}
.prose em {
@apply text-theme-quaternary;
}
.prose h2 { @apply before:font-light before:text-theme-secondary; }
.prose h3 { @apply before:font-light before:text-theme-tertiary; }
.prose h4 { @apply before:font-light before:text-theme-quaternary; }
.prose h1 {
@apply before:content-['#'];
}
.prose h2 {
@apply before:content-['##'];
}
.prose h3 {
@apply before:content-['###'];
}
.prose h4 {
@apply before:content-['####'];
}
.prose pre {
@apply p-0;
}
.prose img {
@apply rounded-lg;
}
}
@layer components {
/* Set width and color for identity icons */
a[rel] svg {
width: 1em;
fill: rgb(var(--color-foreground));
}
.alert {
@apply p-3 mt-3 rounded;
}
.alert.alert-danger {
@apply bg-red-100 text-red-500 dark:bg-red-950 dark:text-red-500;
}
.footnotes li p { display: inline; }
.footnotes hr { display: none; }
.footnote:before { content: '{'; }
.footnote:after { content: '}'; }
a.button {
@apply inline-block text-theme-base px-3 py-2.5 border border-theme-background1 hover:bg-theme-background1 rounded transition font-semibold;
}
}

View file

@ -1,7 +0,0 @@
/*!
Theme: Gruvbox dark, hard
Author: Dawid Kurek (dawikur@gmail.com), morhetz (https://github.com/morhetz/gruvbox)
License: ~ MIT (or more permissive) [via base16-schemes-source]
Maintainer: @highlightjs/core-team
Version: 2021.09.0
*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#d5c4a1;background:#1d2021}.hljs ::selection,.hljs::selection{background-color:#504945;color:#d5c4a1}.hljs-comment{color:#665c54}.hljs-tag{color:#bdae93}.hljs-operator,.hljs-punctuation,.hljs-subst{color:#d5c4a1}.hljs-operator{opacity:.7}.hljs-bullet,.hljs-deletion,.hljs-name,.hljs-selector-tag,.hljs-template-variable,.hljs-variable{color:#fb4934}.hljs-attr,.hljs-link,.hljs-literal,.hljs-number,.hljs-symbol,.hljs-variable.constant_{color:#fe8019}.hljs-class .hljs-title,.hljs-title,.hljs-title.class_{color:#fabd2f}.hljs-strong{font-weight:700;color:#fabd2f}.hljs-addition,.hljs-code,.hljs-string,.hljs-title.class_.inherited__{color:#b8bb26}.hljs-built_in,.hljs-doctag,.hljs-keyword.hljs-atrule,.hljs-quote,.hljs-regexp{color:#8ec07c}.hljs-attribute,.hljs-function .hljs-title,.hljs-section,.hljs-title.function_,.ruby .hljs-property{color:#83a598}.diff .hljs-meta,.hljs-keyword,.hljs-template-tag,.hljs-type{color:#d3869b}.hljs-emphasis{color:#d3869b;font-style:italic}.hljs-meta,.hljs-meta .hljs-keyword,.hljs-meta .hljs-string{color:#d65d0e}.hljs-meta .hljs-keyword,.hljs-meta-keyword{font-weight:700}

201
assets/css/hljs.css Normal file
View file

@ -0,0 +1,201 @@
:root {
/*!
Theme: a11y-light
Author: @ericwbailey
Maintainer: @ericwbailey
Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css
*/
& .hljs {
background: #fefefe;
color: #545454;
}
/* Comment */
& .hljs-comment,
& .hljs-quote {
color: #696969;
}
/* Red */
& .hljs-variable,
& .hljs-template-variable,
& .hljs-tag,
& .hljs-name,
& .hljs-selector-id,
& .hljs-selector-class,
& .hljs-regexp,
& .hljs-deletion {
color: #d91e18;
}
/* Orange */
& .hljs-number,
& .hljs-built_in,
& .hljs-literal,
& .hljs-type,
& .hljs-params,
& .hljs-meta,
& .hljs-link {
color: #aa5d00;
}
/* Yellow */
& .hljs-attribute {
color: #aa5d00;
}
/* Green */
& .hljs-string,
& .hljs-symbol,
& .hljs-bullet,
& .hljs-addition {
color: #008000;
}
/* Blue */
& .hljs-title,
& .hljs-section {
color: #007faa;
}
/* Purple */
& .hljs-keyword,
& .hljs-selector-tag {
color: #7928a1;
}
& .hljs-emphasis {
font-style: italic;
}
& .hljs-strong {
font-weight: bold;
}
@media screen and (-ms-high-contrast: active) {
& .hljs-addition,
& .hljs-attribute,
& .hljs-built_in,
& .hljs-bullet,
& .hljs-comment,
& .hljs-link,
& .hljs-literal,
& .hljs-meta,
& .hljs-number,
& .hljs-params,
& .hljs-string,
& .hljs-symbol,
& .hljs-type,
& .hljs-quote {
color: highlight;
}
& .hljs-keyword,
& .hljs-selector-tag {
font-weight: bold;
}
}
}
:root[data-mode=dark] {
/*!
Theme: a11y-dark
Author: @ericwbailey
Maintainer: @ericwbailey
Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css
*/
& .hljs {
background: #2b2b2b;
color: #f8f8f2;
}
/* Comment */
& .hljs-comment,
& .hljs-quote {
color: #d4d0ab;
}
/* Red */
& .hljs-variable,
& .hljs-template-variable,
& .hljs-tag,
& .hljs-name,
& .hljs-selector-id,
& .hljs-selector-class,
& .hljs-regexp,
& .hljs-deletion {
color: #ffa07a;
}
/* Orange */
& .hljs-number,
& .hljs-built_in,
& .hljs-literal,
& .hljs-type,
& .hljs-params,
& .hljs-meta,
& .hljs-link {
color: #f5ab35;
}
/* Yellow */
& .hljs-attribute {
color: #ffd700;
}
/* Green */
& .hljs-string,
& .hljs-symbol,
& .hljs-bullet,
& .hljs-addition {
color: #abe338;
}
/* Blue */
& .hljs-title,
& .hljs-section {
color: #00e0e0;
}
/* Purple */
& .hljs-keyword,
& .hljs-selector-tag {
color: #dcc6e0;
}
& .hljs-emphasis {
font-style: italic;
}
& .hljs-strong {
font-weight: bold;
}
@media screen and (-ms-high-contrast: active) {
& .hljs-addition,
& .hljs-attribute,
& .hljs-built_in,
& .hljs-bullet,
& .hljs-comment,
& .hljs-link,
& .hljs-literal,
& .hljs-meta,
& .hljs-number,
& .hljs-params,
& .hljs-string,
& .hljs-symbol,
& .hljs-type,
& .hljs-quote {
color: highlight;
}
& .hljs-keyword,
& .hljs-selector-tag {
font-weight: bold;
}
}
}

View file

@ -1,52 +0,0 @@
/*
1. Use a more-intuitive box-sizing model.
*/
*, *::before, *::after {
box-sizing: border-box;
}
/*
2. Remove default margin
*/
* {
margin: 0;
}
/*
3. Allow percentage-based heights in the application
*/
html, body {
height: 100%;
}
/*
Typographic tweaks!
4. Add accessible line-height
5. Improve text rendering
*/
body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
/*
6. Improve media defaults
*/
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
/*
7. Remove built-in form typography styles
*/
input, button, textarea, select {
font: inherit;
}
/*
8. Avoid text overflows
*/
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
/*
9. Create a root stacking context
*/
#root, #__next {
isolation: isolate;
}

View file

@ -0,0 +1,117 @@
/*!
Theme: Tokyo-night-Dark
origin: https://github.com/enkia/tokyo-night-vscode-theme
Description: Original highlight.js style
Author: (c) Henri Vandersleyen <hvandersleyen@gmail.com>
License: see project LICENSE
Touched: 2022
*/
/* Comment */
.hljs-meta,
.hljs-comment {
color: #565f89;
}
/* Red */
/*INFO: This keyword, HTML elements, Regex group symbol, CSS units, Terminal Red */
.hljs-tag,
.hljs-doctag,
.hljs-selector-id,
.hljs-selector-class,
.hljs-regexp,
.hljs-template-tag,
.hljs-selector-pseudo,
.hljs-selector-attr,
.hljs-variable.language_,
.hljs-deletion {
color: #f7768e;
}
/*Orange */
/*INFO: Number and Boolean constants, Language support constants */
.hljs-variable,
.hljs-template-variable,
.hljs-number,
.hljs-literal,
.hljs-type,
.hljs-params,
.hljs-link {
color: #ff9e64;
}
/* Yellow */
/* INFO: Function parameters, Regex character sets, Terminal Yellow */
.hljs-built_in,
.hljs-attribute {
color: #e0af68;
}
/* cyan */
/* INFO: Language support functions, CSS HTML elements */
.hljs-selector-tag {
color: #2ac3de;
}
/* light blue */
/* INFO: Object properties, Regex quantifiers and flags, Markdown headings, Terminal Cyan, Markdown code, Import/export keywords */
.hljs-keyword,
.hljs-title.function_,
.hljs-title,
.hljs-title.class_,
.hljs-title.class_.inherited__,
.hljs-subst,
.hljs-property {color: #7dcfff;}
/*Green*/
/* INFO: Object literal keys, Markdown links, Terminal Green */
.hljs-selector-tag { color: #73daca;}
/*Green(er) */
/* INFO: Strings, CSS class names */
.hljs-quote,
.hljs-string,
.hljs-symbol,
.hljs-bullet,
.hljs-addition {
color: #9ece6a;
}
/* Blue */
/* INFO: Function names, CSS property names, Terminal Blue */
.hljs-code,
.hljs-formula,
.hljs-section {
color: #7aa2f7;
}
/* Magenta */
/*INFO: Control Keywords, Storage Types, Regex symbols and operators, HTML Attributes, Terminal Magenta */
.hljs-name,
.hljs-keyword,
.hljs-operator,
.hljs-keyword,
.hljs-char.escape_,
.hljs-attr {
color: #bb9af7;
}
/* white*/
/* INFO: Variables, Class names, Terminal White */
.hljs-punctuation {color: #c0caf5}
.hljs {
background: #1a1b26;
color: #9aa5ce;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}

View file

@ -1,29 +1,10 @@
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"
// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
// import "../vendor/some-package.js"
//
// Alternatively, you can `npm install some-package --prefix assets` and import
// them using a path starting with the package name:
//
// import "some-package"
//
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration.
import { Socket } from "phoenix"
import { LiveSocket } from "phoenix_live_view"
import topbar from "../vendor/topbar"
import React from "react"
import { createRoot } from 'react-dom/client'
import KBar from "./kbar"
import darkmode from "./darkmode"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, { params: { _csrf_token: csrfToken } })
@ -42,41 +23,24 @@ liveSocket.connect()
// >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket
const reactRoot = document.querySelector('#react-root')
if (reactRoot) {
const root = createRoot(reactRoot);
root.render(<KBar/>);
}
document.addEventListener("DOMContentLoaded", function() {
darkmode()
document
.querySelector("#dark-mode-toggle")
.addEventListener("click", (e) => {
e.preventDefault()
const data = document.documentElement.dataset
if (data["mode"] && data["mode"] == "dark") {
delete data["mode"]
window.localStorage.removeItem("theme")
} else {
data["mode"] = "dark"
window.localStorage.setItem("theme", "dark")
}
})
document
.querySelectorAll('textarea')
.forEach(e => e.addEventListener('keydown', function(e) {
if (e.key == 'Tab') {
e.preventDefault();
var start = this.selectionStart;
var end = this.selectionEnd;
document
.querySelectorAll('textarea')
.forEach(e => e.addEventListener('keydown', function(e) {
if (e.key == 'Tab') {
e.preventDefault();
var start = this.selectionStart;
var end = this.selectionEnd;
// set textarea value to: text before caret + tab + text after caret
this.value = this.value.substring(0, start) +
"\t" + this.value.substring(end);
// set textarea value to: text before caret + tab + text after caret
this.value = this.value.substring(0, start) +
"\t" + this.value.substring(end);
// put caret at right position again
this.selectionStart =
this.selectionEnd = start + 1;
}
}))
// put caret at right position again
this.selectionStart =
this.selectionEnd = start + 1;
}
}))
})

15
assets/js/darkmode.js Normal file
View file

@ -0,0 +1,15 @@
export default function() {
document
.querySelector('#dark-mode-toggle')
.addEventListener('click', (e) => {
e.preventDefault()
const data = document.documentElement.dataset
if (data['mode'] && data['mode'] == 'dark') {
delete data['mode']
window.localStorage.removeItem('theme')
} else {
data['mode'] = 'dark'
window.localStorage.setItem('theme', 'dark')
}
})
}

View file

@ -1,267 +0,0 @@
import React, {useState, useEffect, useMemo} from 'react'
import {
KBarProvider,
KBarPortal,
KBarPositioner,
KBarAnimator,
KBarSearch,
KBarResults,
useMatches,
useRegisterActions,
useKBar,
createAction
} from "kbar";
import classNames from 'classnames';
const searchStyle = {
padding: "12px 16px",
fontSize: "16px",
width: "100%",
boxSizing: "border-box",
outline: "none"
};
const animatorStyle = {
maxWidth: "600px",
width: "100%",
borderRadius: "8px",
overflow: "hidden"
};
const groupNameStyle = {
padding: "8px 16px",
fontSize: "10px",
textTransform: "uppercase",
opacity: 0.75,
};
function RenderResults() {
const { results, rootActionId } = useMatches();
return (
<KBarResults
items={results}
onRender={({ item, active }) =>
typeof item === "string" ? (
<div style={groupNameStyle} className="dark:text-white">{item}</div>
) : (
<ResultItem
action={item}
active={active}
currentRootActionId={rootActionId}
/>
)
}
/>
);
}
const ResultItem = React.forwardRef(
(
{
action,
active,
currentRootActionId,
},
ref
) => {
const ancestors = React.useMemo(() => {
if (!currentRootActionId) return action.ancestors;
const index = action.ancestors.findIndex(
(ancestor) => ancestor.id === currentRootActionId
);
// +1 removes the currentRootAction; e.g.
// if we are on the "Set theme" parent action,
// the UI should not display "Set theme… > Dark"
// but rather just "Dark"
return action.ancestors.slice(index + 1);
}, [action.ancestors, currentRootActionId]);
return (
<div
ref={ref}
className={classNames(
"flex justify-between items-center cursor-pointer px-4 py-3 dark:text-white",
{
"bg-gray-300 dark:bg-gray-600": active,
"border-l-2 border-gray-300": active
})}
>
<div className="flex gap-3 items-center text-sm dark:text-white">
{action.icon && action.icon}
<div className="flex flex-col">
<div>
{ancestors.length > 0 &&
ancestors.map((ancestor) => (
<React.Fragment key={ancestor.id}>
<span
style={{
opacity: 0.5,
marginRight: 8,
}}
>
{ancestor.name}
</span>
<span
style={{
marginRight: 8,
}}
>
&rsaquo;
</span>
</React.Fragment>
))}
<span>{action.name}</span>
</div>
{action.subtitle && (
<span style={{ fontSize: 12 }}>{action.subtitle}</span>
)}
</div>
</div>
{action.shortcut?.length ? (
<div
aria-hidden
style={{ display: "grid", gridAutoFlow: "column", gap: "4px" }}
>
{action.shortcut.map((sc) => (
<kbd
key={sc}
style={{
padding: "4px 6px",
background: "rgba(0 0 0 / .1)",
borderRadius: "4px",
fontSize: 14,
}}
>
{sc}
</kbd>
))}
</div>
) : null}
</div>
);
}
);
const staticActions = [
{
id: "user",
name: "User",
shortcut: ["u"],
keywords: "profile",
perform: () => (window.location.pathname = "user"),
},
{
id: "user.edit",
name: "Edit User",
shortcut: ["u e"],
keywords: "profile edit settings",
perform: () => (window.location.pathname = "user/settings"),
},
{
id: "admin",
name: "Admin",
shortcut: ["a"],
keywords: "home",
perform: () => (window.location.pathname = "admin"),
},
{
id: "notes",
name: "Notes",
shortcut: ["n"],
keywords: "posts",
perform: () => (window.location.pathname = "admin/notes"),
},
{
id: "notes.new",
name: "New Note",
shortcut: ["n n"],
keywords: "create new",
perform: () => (window.location.pathname = "admin/notes/new"),
},
{
id: "channels",
name: "Channels",
shortcut: ["c"],
keywords: "channels",
perform: () => (window.location.pathname = "admin/channels"),
},
{
id: "channels.new",
name: "New Channel",
shortcut: ["n c"],
keywords: "create new",
perform: () => (window.location.pathname = "admin/channels/new"),
},
{
id: "identities",
name: "Identities",
shortcut: ["i"],
keywords: "identities",
perform: () => (window.location.pathname = "admin/identities"),
},
{
id: "identities.new",
name: "New Identity",
shortcut: ["n i"],
keywords: "create new",
perform: () => (window.location.pathname = "admin/identities/new"),
},
{
id: "settings",
name: "Settings",
shortcut: ["s"],
keywords: "settings",
perform: () => (window.location.pathname = "admin/settings"),
},
{
id: "settings.edit",
name: "Edit Settings",
shortcut: ["s e"],
keywords: "settings edit",
perform: () => (window.location.pathname = "admin/settings/edit"),
}
].map(function(a) {
return {...a, section: "Pages"}
})
function DynamicResultsProvider() {
const [actions, setActions] = useState([])
const [notes, setNotes] = useState([])
const [rerender, setRerender] = useState(true)
useEffect(() => {
fetch("/api/admin/notes")
.then(resp => resp.json())
.then(json => setNotes(json.notes))
}, [rerender])
const noteActions = useMemo(() => notes.map(note => createAction({
id: note.slug,
name: note.name,
subtitle: note.channels.map(c => c.name).join(", "),
section: "Notes",
keywords: note.channels.map(c => c.name),
perform: () => (window.location.pathname = `/admin/notes/${note.id}`),
})), [notes])
useRegisterActions([...staticActions, ...noteActions], [noteActions])
}
export default function KBar() {
return (
<KBarProvider actions={staticActions}>
<DynamicResultsProvider />
<KBarPortal>
<KBarPositioner>
<KBarAnimator style={animatorStyle} className="bg-gray-50 border border-gray-100 shadow-sm dark:bg-gray-900 dark:border-gray-800">
<KBarSearch style={searchStyle} className="bg-gray-50 dark:bg-gray-900 border-b border-gray-100 text-gray-900 dark:text-gray-100" />
<RenderResults />
</KBarAnimator>
</KBarPositioner>
</KBarPortal>
</KBarProvider>
)
}

View file

@ -4,6 +4,7 @@ import 'phoenix_html'
import hljs from 'highlight.js'
import GLightbox from 'glightbox'
import Tablesort from 'tablesort'
import darkmode from "./darkmode"
document.addEventListener('DOMContentLoaded', (event) => {
document.querySelectorAll('.prose pre code').forEach((el) =>
@ -11,22 +12,8 @@ document.addEventListener('DOMContentLoaded', (event) => {
document.querySelectorAll('.prose table').forEach(el =>
new Tablesort(el))
});
document
.querySelector('#dark-mode-toggle')
.addEventListener('click', (e) => {
e.preventDefault()
const data = document.documentElement.dataset
if (data['mode'] && data['mode'] == 'dark') {
delete data['mode']
window.localStorage.removeItem('theme')
} else {
data['mode'] = 'dark'
window.localStorage.setItem('theme', 'dark')
}
})
darkmode()
GLightbox({ selector: '.lightbox' })
window.hljs = hljs
GLightbox({ selector: '.lightbox' })
});

397
assets/package-lock.json generated
View file

@ -8,46 +8,30 @@
"classnames": "^2.3.2",
"glightbox": "^3.2.0",
"highlight.js": "^11.8.0",
"kbar": "^0.1.0-beta.43",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view",
"tablesort": "^5.3.0"
},
"devDependencies": {
"esbuild": "^0.18.0"
},
"peerDependencies": {
"react": "^18.0",
"react-dom": "^18.0"
"esbuild": "^0.19.2"
}
},
"../deps/phoenix": {
"version": "1.7.6",
"version": "1.7.7",
"license": "MIT"
},
"../deps/phoenix_html": {
"version": "3.3.1"
"version": "3.3.2"
},
"../deps/phoenix_live_view": {
"version": "0.19.3",
"version": "0.19.5",
"license": "MIT"
},
"node_modules/@babel/runtime": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz",
"integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==",
"dependencies": {
"regenerator-runtime": "^0.13.11"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.17.tgz",
"integrity": "sha512-wHsmJG/dnL3OkpAcwbgoBTTMHVi4Uyou3F5mf58ZtmUyIKfcdA7TROav/6tCzET4A3QW2Q2FC+eFneMU+iyOxg==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.2.tgz",
"integrity": "sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q==",
"cpu": [
"arm"
],
@ -61,9 +45,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.17.tgz",
"integrity": "sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.2.tgz",
"integrity": "sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw==",
"cpu": [
"arm64"
],
@ -77,9 +61,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.17.tgz",
"integrity": "sha512-O+FeWB/+xya0aLg23hHEM2E3hbfwZzjqumKMSIqcHbNvDa+dza2D0yLuymRBQQnC34CWrsJUXyH2MG5VnLd6uw==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.2.tgz",
"integrity": "sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w==",
"cpu": [
"x64"
],
@ -93,9 +77,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.17.tgz",
"integrity": "sha512-M9uJ9VSB1oli2BE/dJs3zVr9kcCBBsE883prage1NWz6pBS++1oNn/7soPNS3+1DGj0FrkSvnED4Bmlu1VAE9g==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.2.tgz",
"integrity": "sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA==",
"cpu": [
"arm64"
],
@ -109,9 +93,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.17.tgz",
"integrity": "sha512-XDre+J5YeIJDMfp3n0279DFNrGCXlxOuGsWIkRb1NThMZ0BsrWXoTg23Jer7fEXQ9Ye5QjrvXpxnhzl3bHtk0g==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.2.tgz",
"integrity": "sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw==",
"cpu": [
"x64"
],
@ -125,9 +109,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.17.tgz",
"integrity": "sha512-cjTzGa3QlNfERa0+ptykyxs5A6FEUQQF0MuilYXYBGdBxD3vxJcKnzDlhDCa1VAJCmAxed6mYhA2KaJIbtiNuQ==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.2.tgz",
"integrity": "sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ==",
"cpu": [
"arm64"
],
@ -141,9 +125,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.17.tgz",
"integrity": "sha512-sOxEvR8d7V7Kw8QqzxWc7bFfnWnGdaFBut1dRUYtu+EIRXefBc/eIsiUiShnW0hM3FmQ5Zf27suDuHsKgZ5QrA==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.2.tgz",
"integrity": "sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw==",
"cpu": [
"x64"
],
@ -157,9 +141,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.17.tgz",
"integrity": "sha512-2d3Lw6wkwgSLC2fIvXKoMNGVaeY8qdN0IC3rfuVxJp89CRfA3e3VqWifGDfuakPmp90+ZirmTfye1n4ncjv2lg==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.2.tgz",
"integrity": "sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg==",
"cpu": [
"arm"
],
@ -173,9 +157,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.17.tgz",
"integrity": "sha512-c9w3tE7qA3CYWjT+M3BMbwMt+0JYOp3vCMKgVBrCl1nwjAlOMYzEo+gG7QaZ9AtqZFj5MbUc885wuBBmu6aADQ==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.2.tgz",
"integrity": "sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg==",
"cpu": [
"arm64"
],
@ -189,9 +173,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.17.tgz",
"integrity": "sha512-1DS9F966pn5pPnqXYz16dQqWIB0dmDfAQZd6jSSpiT9eX1NzKh07J6VKR3AoXXXEk6CqZMojiVDSZi1SlmKVdg==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.2.tgz",
"integrity": "sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ==",
"cpu": [
"ia32"
],
@ -205,9 +189,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.17.tgz",
"integrity": "sha512-EvLsxCk6ZF0fpCB6w6eOI2Fc8KW5N6sHlIovNe8uOFObL2O+Mr0bflPHyHwLT6rwMg9r77WOAWb2FqCQrVnwFg==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.2.tgz",
"integrity": "sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw==",
"cpu": [
"loong64"
],
@ -221,9 +205,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.17.tgz",
"integrity": "sha512-e0bIdHA5p6l+lwqTE36NAW5hHtw2tNRmHlGBygZC14QObsA3bD4C6sXLJjvnDIjSKhW1/0S3eDy+QmX/uZWEYQ==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.2.tgz",
"integrity": "sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg==",
"cpu": [
"mips64el"
],
@ -237,9 +221,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.17.tgz",
"integrity": "sha512-BAAilJ0M5O2uMxHYGjFKn4nJKF6fNCdP1E0o5t5fvMYYzeIqy2JdAP88Az5LHt9qBoUa4tDaRpfWt21ep5/WqQ==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.2.tgz",
"integrity": "sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw==",
"cpu": [
"ppc64"
],
@ -253,9 +237,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.17.tgz",
"integrity": "sha512-Wh/HW2MPnC3b8BqRSIme/9Zhab36PPH+3zam5pqGRH4pE+4xTrVLx2+XdGp6fVS3L2x+DrsIcsbMleex8fbE6g==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.2.tgz",
"integrity": "sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw==",
"cpu": [
"riscv64"
],
@ -269,9 +253,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.17.tgz",
"integrity": "sha512-j/34jAl3ul3PNcK3pfI0NSlBANduT2UO5kZ7FCaK33XFv3chDhICLY8wJJWIhiQ+YNdQ9dxqQctRg2bvrMlYgg==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.2.tgz",
"integrity": "sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g==",
"cpu": [
"s390x"
],
@ -285,9 +269,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.17.tgz",
"integrity": "sha512-QM50vJ/y+8I60qEmFxMoxIx4de03pGo2HwxdBeFd4nMh364X6TIBZ6VQ5UQmPbQWUVWHWws5MmJXlHAXvJEmpQ==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.2.tgz",
"integrity": "sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ==",
"cpu": [
"x64"
],
@ -301,9 +285,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.17.tgz",
"integrity": "sha512-/jGlhWR7Sj9JPZHzXyyMZ1RFMkNPjC6QIAan0sDOtIo2TYk3tZn5UDrkE0XgsTQCxWTTOcMPf9p6Rh2hXtl5TQ==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.2.tgz",
"integrity": "sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ==",
"cpu": [
"x64"
],
@ -317,9 +301,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.17.tgz",
"integrity": "sha512-rSEeYaGgyGGf4qZM2NonMhMOP/5EHp4u9ehFiBrg7stH6BYEEjlkVREuDEcQ0LfIl53OXLxNbfuIj7mr5m29TA==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.2.tgz",
"integrity": "sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw==",
"cpu": [
"x64"
],
@ -333,9 +317,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.17.tgz",
"integrity": "sha512-Y7ZBbkLqlSgn4+zot4KUNYst0bFoO68tRgI6mY2FIM+b7ZbyNVtNbDP5y8qlu4/knZZ73fgJDlXID+ohY5zt5g==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.2.tgz",
"integrity": "sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw==",
"cpu": [
"x64"
],
@ -349,9 +333,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.17.tgz",
"integrity": "sha512-bwPmTJsEQcbZk26oYpc4c/8PvTY3J5/QK8jM19DVlEsAB41M39aWovWoHtNm78sd6ip6prilxeHosPADXtEJFw==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.2.tgz",
"integrity": "sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg==",
"cpu": [
"arm64"
],
@ -365,9 +349,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.17.tgz",
"integrity": "sha512-H/XaPtPKli2MhW+3CQueo6Ni3Avggi6hP/YvgkEe1aSaxw+AeO8MFjq8DlgfTd9Iz4Yih3QCZI6YLMoyccnPRg==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.2.tgz",
"integrity": "sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA==",
"cpu": [
"ia32"
],
@ -381,9 +365,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.17.tgz",
"integrity": "sha512-fGEb8f2BSA3CW7riJVurug65ACLuQAzKq0SSqkY2b2yHHH0MzDfbLyKIGzHwOI/gkHcxM/leuSW6D5w/LMNitA==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.2.tgz",
"integrity": "sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw==",
"cpu": [
"x64"
],
@ -396,101 +380,15 @@
"node": ">=12"
}
},
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
"integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-portal": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.3.tgz",
"integrity": "sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-primitive": "1.0.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-primitive": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz",
"integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-slot": "1.0.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@reach/observe-rect": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@reach/observe-rect/-/observe-rect-1.2.0.tgz",
"integrity": "sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ=="
},
"node_modules/classnames": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
},
"node_modules/esbuild": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.17.tgz",
"integrity": "sha512-1GJtYnUxsJreHYA0Y+iQz2UEykonY66HNWOb0yXYZi9/kNrORUEHVg87eQsCtqh59PEJ5YVZJO98JHznMJSWjg==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.2.tgz",
"integrity": "sha512-G6hPax8UbFakEj3hWO0Vs52LQ8k3lnBhxZWomUJDxfz3rZTLqF5k/FCzuNdLx2RbpBiQQF9H9onlDDH1lZsnjg==",
"dev": true,
"hasInstallScript": true,
"bin": {
@ -500,41 +398,28 @@
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.18.17",
"@esbuild/android-arm64": "0.18.17",
"@esbuild/android-x64": "0.18.17",
"@esbuild/darwin-arm64": "0.18.17",
"@esbuild/darwin-x64": "0.18.17",
"@esbuild/freebsd-arm64": "0.18.17",
"@esbuild/freebsd-x64": "0.18.17",
"@esbuild/linux-arm": "0.18.17",
"@esbuild/linux-arm64": "0.18.17",
"@esbuild/linux-ia32": "0.18.17",
"@esbuild/linux-loong64": "0.18.17",
"@esbuild/linux-mips64el": "0.18.17",
"@esbuild/linux-ppc64": "0.18.17",
"@esbuild/linux-riscv64": "0.18.17",
"@esbuild/linux-s390x": "0.18.17",
"@esbuild/linux-x64": "0.18.17",
"@esbuild/netbsd-x64": "0.18.17",
"@esbuild/openbsd-x64": "0.18.17",
"@esbuild/sunos-x64": "0.18.17",
"@esbuild/win32-arm64": "0.18.17",
"@esbuild/win32-ia32": "0.18.17",
"@esbuild/win32-x64": "0.18.17"
}
},
"node_modules/fast-equals": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-2.0.4.tgz",
"integrity": "sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w=="
},
"node_modules/fuse.js": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz",
"integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==",
"engines": {
"node": ">=10"
"@esbuild/android-arm": "0.19.2",
"@esbuild/android-arm64": "0.19.2",
"@esbuild/android-x64": "0.19.2",
"@esbuild/darwin-arm64": "0.19.2",
"@esbuild/darwin-x64": "0.19.2",
"@esbuild/freebsd-arm64": "0.19.2",
"@esbuild/freebsd-x64": "0.19.2",
"@esbuild/linux-arm": "0.19.2",
"@esbuild/linux-arm64": "0.19.2",
"@esbuild/linux-ia32": "0.19.2",
"@esbuild/linux-loong64": "0.19.2",
"@esbuild/linux-mips64el": "0.19.2",
"@esbuild/linux-ppc64": "0.19.2",
"@esbuild/linux-riscv64": "0.19.2",
"@esbuild/linux-s390x": "0.19.2",
"@esbuild/linux-x64": "0.19.2",
"@esbuild/netbsd-x64": "0.19.2",
"@esbuild/openbsd-x64": "0.19.2",
"@esbuild/sunos-x64": "0.19.2",
"@esbuild/win32-arm64": "0.19.2",
"@esbuild/win32-ia32": "0.19.2",
"@esbuild/win32-x64": "0.19.2"
}
},
"node_modules/glightbox": {
@ -550,54 +435,6 @@
"node": ">=12.0.0"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"peer": true
},
"node_modules/kbar": {
"version": "0.1.0-beta.43",
"resolved": "https://registry.npmjs.org/kbar/-/kbar-0.1.0-beta.43.tgz",
"integrity": "sha512-MmhhvGuZfmA616X9wuy/iaWCPFmlEi6kGkvce/7GlatWmCSkHZhD8glxUruFUpxSN0HZvW/6e/jvSgpRGnC76w==",
"dependencies": {
"@radix-ui/react-portal": "^1.0.1",
"fast-equals": "^2.0.3",
"fuse.js": "^6.6.2",
"react-virtual": "^2.8.2",
"tiny-invariant": "^1.2.0"
},
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/kbar/node_modules/react-virtual": {
"version": "2.10.4",
"resolved": "https://registry.npmjs.org/react-virtual/-/react-virtual-2.10.4.tgz",
"integrity": "sha512-Ir6+oPQZTVHfa6+JL9M7cvMILstFZH/H3jqeYeKI4MSUX+rIruVwFC6nGVXw9wqAw8L0Kg2KvfXxI85OvYQdpQ==",
"funding": [
"https://github.com/sponsors/tannerlinsley"
],
"dependencies": {
"@reach/observe-rect": "^1.1.0"
},
"peerDependencies": {
"react": "^16.6.3 || ^17.0.0"
}
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"peer": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/phoenix": {
"resolved": "../deps/phoenix",
"link": true
@ -610,54 +447,10 @@
"resolved": "../deps/phoenix_live_view",
"link": true
},
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.0"
},
"peerDependencies": {
"react": "^18.2.0"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/tablesort": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/tablesort/-/tablesort-5.3.0.tgz",
"integrity": "sha512-WkfcZBHsp47gVH9CBHG0ZXopriG01IA87arGrchvIe868d4RiXVvoYPS1zMq9IdW05kBs5iGsqxTABqLyWonbg=="
},
"node_modules/tiny-invariant": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
}
}
}

View file

@ -1,19 +1,14 @@
{
"devDependencies": {
"esbuild": "^0.18.0"
"esbuild": "^0.19.2"
},
"dependencies": {
"classnames": "^2.3.2",
"glightbox": "^3.2.0",
"highlight.js": "^11.8.0",
"kbar": "^0.1.0-beta.40",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view",
"tablesort": "^5.3.0"
},
"peerDependencies": {
"react": "^18.0",
"react-dom": "^18.0"
}
}

View file

@ -14,59 +14,47 @@ module.exports = {
],
darkMode: ['class', '[data-mode="dark"]'],
theme: {
container: { center: true },
extend: {
typography: {
gruvbox: {
css: {
'--tw-prose-td-borders': 'rgb(var(--color-background1))',
'--tw-prose-th-borders': 'rgb(var(--color-background1))',
'--tw-prose-body': 'rgb(var(--color-foreground))',
'--tw-prose-links': 'rgb(var(--color-foreground))',
'--tw-prose-headings': 'rgb(var(--color-foreground))',
'--tw-prose-bold': 'rgb(var(--color-yellow1))',
'--tw-prose-quotes': 'rgb(var(--color-foreground))',
'--tw-prose-bullets': 'rgb(var(--color-primary))',
'--tw-prose-code': 'rgb(var(--color-yellow1))',
'--tw-prose-pre-bg': 'rgb(var(--color-background1))',
'--tw-prose-quote-borders': 'rgb(var(--color-primary))',
'--tw-prose-counters': 'rgb(var(--color-foreground))',
'--tw-prose-invert-td-borders': 'rgb(var(--color-background1))',
'--tw-prose-invert-th-borders': 'rgb(var(--color-background1))',
'--tw-prose-invert-body': 'rgb(var(--color-foreground))',
'--tw-prose-invert-links': 'rgb(var(--color-foreground))',
'--tw-prose-invert-headings': 'rgb(var(--color-foreground))',
'--tw-prose-invert-bold': 'rgb(var(--color-yellow))',
'--tw-prose-invert-quotes': 'rgb(var(--color-foreground))',
'--tw-prose-invert-bullets': 'rgb(var(--color-primary))',
'--tw-prose-invert-quote-borders': 'rgb(var(--color-primary))',
'--tw-prose-invert-code': 'rgb(var(--color-yellow))',
'--tw-prose-invert-pre-bg': 'rgb(var(--color-background1))',
'--tw-prose-invert-counters': 'rgb(var(--color-foreground))',
}
}
},
colors: {
code: colors.emerald,
gray: colors.zinc,
theme: {
background: 'rgb(var(--color-background) / <alpha-value>)',
background1: 'rgb(var(--color-background1) / <alpha-value>)',
base: 'rgb(var(--color-foreground) / <alpha-value>)',
base1: 'rgb(var(--color-foreground1) / <alpha-value>)',
primary: 'rgb(var(--color-primary) / <alpha-value>)',
primary1: 'rgb(var(--color-primary1) / <alpha-value>)',
secondary: 'rgb(var(--color-secondary) / <alpha-value>)',
secondary1: 'rgb(var(--color-secondary1) / <alpha-value>)',
tertiary: 'rgb(var(--color-blue) / <alpha-value>)',
tertiary1: 'rgb(var(--color-blue1) / <alpha-value>)',
quaternary: 'rgb(var(--color-purple) / <alpha-value>)',
quaternary1: 'rgb(var(--color-purple1) / <alpha-value>)',
quinary: 'rgb(var(--color-yellow) / <alpha-value>)',
quinary1: 'rgb(var(--color-yellow1) / <alpha-value>)',
heading: 'rgb(var(--color-heading) / <alpha-value>)'
}
}
},
primary: colors.blue,
neutral: colors.slate,
foreground: 'rgb(var(--color-foreground) / <alpha-value>)',
background: 'rgb(var(--color-background) / <alpha-value>)'
},
typography: ({ theme }) => ({
inhji: {
css: {
'--tw-prose-lead': theme('colors.neutral[700]'),
'--tw-prose-links': theme('colors.neutral[900]'),
'--tw-prose-counters': theme('colors.neutral[600]'),
'--tw-prose-bullets': theme('colors.neutral[400]'),
'--tw-prose-hr': theme('colors.neutral[300]'),
'--tw-prose-quotes': theme('colors.neutral[900]'),
'--tw-prose-quote-borders': theme('colors.neutral[300]'),
'--tw-prose-captions': theme('colors.neutral[700]'),
'--tw-prose-code': theme('colors.neutral[900]'),
'--tw-prose-pre-code': theme('colors.neutral[100]'),
'--tw-prose-pre-bg': theme('colors.neutral[200]'),
'--tw-prose-th-borders': theme('colors.neutral[300]'),
'--tw-prose-td-borders': theme('colors.neutral[200]'),
'--tw-prose-invert-lead': theme('colors.neutral[300]'),
'--tw-prose-invert-links': theme('colors.white'),
'--tw-prose-invert-counters': theme('colors.neutral[400]'),
'--tw-prose-invert-bullets': theme('colors.neutral[600]'),
'--tw-prose-invert-hr': theme('colors.neutral[700]'),
'--tw-prose-invert-quotes': theme('colors.neutral[100]'),
'--tw-prose-invert-quote-borders': theme('colors.neutral[700]'),
'--tw-prose-invert-captions': theme('colors.neutral[400]'),
'--tw-prose-invert-code': theme('colors.white'),
'--tw-prose-invert-pre-code': theme('colors.neutral[300]'),
'--tw-prose-invert-pre-bg': 'rgb(0 0 0 / 50%)',
'--tw-prose-invert-th-borders': theme('colors.neutral[600]'),
'--tw-prose-invert-td-borders': theme('colors.neutral[700]'),
},
},
}),
}
},
plugins: [
require("@tailwindcss/forms"),

View file

@ -14,9 +14,8 @@ config :chiya, ChiyaWeb.Endpoint, cache_static_manifest: "priv/static/cache_mani
# Configures Swoosh API Client
config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: Chiya.Finch
# Do not print debug messages in production
# config :logger, level: :debug
config :logger, :default_handler, level: :info
# Do print debug messages in production
config :logger, :default_handler, level: :debug
config :cors_plug,
origin: ["app://obsidian.md"],

3
lib/chiya/flop.ex Normal file
View file

@ -0,0 +1,3 @@
defmodule Chiya.Flop do
use Flop, repo: Chiya.Repo, default_limit: 10
end

View file

@ -12,7 +12,7 @@ defmodule Chiya.Notes do
:images,
:links_from,
:links_to,
:tags,
tags: [:notes],
comments:
from(c in NoteComment,
order_by: [
@ -39,6 +39,34 @@ defmodule Chiya.Notes do
|> Repo.preload(@preloads)
end
def list_admin_notes(params) do
q =
Note
|> join(:inner, [n], c in assoc(n, :channels), as: :channels)
|> order_by([n, c], desc: n.updated_at, desc: n.published_at)
Chiya.Flop.validate_and_run(q, params, for: Chiya.Notes.Note)
end
def list_home_notes(channel, params) do
q =
list_notes_by_channel_query(channel)
|> where([n], not is_nil(n.published_at))
|> order_by([n], desc: n.updated_at, desc: n.published_at)
|> preload(^@preloads)
Chiya.Flop.validate_and_run(q, params, for: Chiya.Notes.Note)
end
def list_apply_notes(%Chiya.Tags.Tag{} = tag) do
Note
|> where([n], fragment("? ~ ?", n.name, ^tag.regex))
|> or_where([n], fragment("? ~ ?", n.url, ^tag.regex))
|> or_where([n], fragment("? ~ ?", n.content, ^tag.regex))
|> preload(^@preloads)
|> Repo.all()
end
def list_notes_by_channel(%Chiya.Channels.Channel{} = channel) do
list_notes_by_channel_query(channel)
|> order_by([n], desc: n.updated_at, desc: n.published_at)

View file

@ -11,6 +11,22 @@ defmodule Chiya.Notes.Note do
@reserved_slugs ~w(user admin dev api)
@note_url_regex ~r/\/note\/([a-z0-9-]+)/
@derive {
Flop.Schema,
filterable: [:name, :channels],
sortable: [:name],
default_limit: 10,
max_limit: 100,
adapter_opts: [
join_fields: [
channels: [
binding: :channels,
field: :name,
ecto_type: :string
]
]
]
}
@derive {Jason.Encoder, only: [:id, :name, :content, :slug, :channels, :tags]}
schema "notes" do
field :content, :string
@ -56,7 +72,7 @@ defmodule Chiya.Notes.Note do
end
def note_path_admin(note) do
~p"/admin/notes/#{note.slug}"
~p"/admin/notes/#{note.id}"
end
def note_url(note) do
@ -98,6 +114,16 @@ defmodule Chiya.Notes.Note do
end
end
def note_excerpt(note_content) do
if String.contains?(note_content, "<!-- more -->") do
note_content
|> String.split("<!-- more -->")
|> List.first()
else
String.slice(note_content, 0..150) <> ".."
end
end
@doc false
def changeset(note, attrs) do
# if you need to have a preloaded note here,

View file

@ -15,5 +15,6 @@ defmodule Chiya.Notes.NoteTag do
note_tag
|> cast(attrs, [:note_id, :tag_id])
|> validate_required([:note_id, :tag_id])
|> unique_constraint([:note_id, :tag_id])
end
end

View file

@ -8,7 +8,7 @@ defmodule Chiya.Tags do
alias Chiya.Tags.Tag
@preloads [notes: [:tags]]
@preloads [notes: [tags: [:notes]]]
defp with_preloads(query), do: preload(query, ^@preloads)
@doc """
@ -38,6 +38,17 @@ defmodule Chiya.Tags do
|> Repo.all()
end
def list_admin_tags() do
q =
Tag
|> order_by(:name)
|> with_preloads()
Repo.all(q)
end
def get_tag!(id), do: Repo.get!(Tag, id) |> preload_tag()
@doc """
Gets a single tag.

View file

@ -6,6 +6,10 @@ defmodule Chiya.Tags.Tag do
import Ecto.Changeset
alias Chiya.Tags.TagSlug
@derive {
Flop.Schema,
filterable: [], sortable: [], default_limit: 100
}
@derive {Jason.Encoder, only: [:name]}
schema "tags" do
field :name, :string

View file

@ -8,6 +8,13 @@ defmodule Chiya.Tags.TagUpdater do
alias Chiya.{Notes, Tags}
alias Chiya.Notes.Note
@doc """
Updates a tag for the given note.
## Examples
iex> update_tags({:ok, note}, "foo,bar")
"""
def update_tags({:ok, %Note{} = note}, attrs) do
note
|> Notes.preload_note()
@ -20,27 +27,19 @@ defmodule Chiya.Tags.TagUpdater do
{:error, changeset}
end
@doc """
Updates the tags for the given note
## Examples
iex> update_tags(note, "foo,bar")
"""
def update_tags(note, %{tags_string: new_tags} = attrs) when is_map(attrs) do
def update_tags(%Note{} = note, %{tags_string: new_tags} = attrs) when is_map(attrs) do
update_tags(note, new_tags)
end
def update_tags(note, %{"tags_string" => new_tags} = attrs) when is_map(attrs) do
def update_tags(%Note{} = note, %{"tags_string" => new_tags} = attrs) when is_map(attrs) do
update_tags(note, new_tags)
end
def update_tags(note, attrs) when is_map(attrs) do
def update_tags(%Note{} = note, attrs) when is_map(attrs) do
note
end
def update_tags(note, new_tags) when is_binary(new_tags) do
def update_tags(%Note{} = note, new_tags) when is_binary(new_tags) do
update_tags(note, split_tags(new_tags))
end
@ -51,7 +50,7 @@ defmodule Chiya.Tags.TagUpdater do
Enum.map(new_tags, fn tag ->
tag
|> String.downcase()
|> Slugger.slugify()
|> Slugger.slugify(45)
end)
Logger.info("Adding tags #{inspect(new_tags -- old_tags)}")
@ -62,14 +61,7 @@ defmodule Chiya.Tags.TagUpdater do
|> remove_tags(old_tags -- new_tags)
end
defp split_tags(tags_string) when is_binary(tags_string) do
tags_string
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.filter(&(String.length(&1) > 0))
end
defp add_tags(note, tags) do
def add_tags(note, tags) do
tags
|> Enum.uniq()
|> Enum.each(&add_tag(note, &1))
@ -77,6 +69,13 @@ defmodule Chiya.Tags.TagUpdater do
note
end
defp split_tags(tags_string) when is_binary(tags_string) do
tags_string
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.filter(&(String.length(&1) > 0))
end
defp add_tag(%{id: note_id} = note, tag) when is_binary(tag) do
slug = Slugger.slugify_downcase(tag)
@ -100,7 +99,14 @@ defmodule Chiya.Tags.TagUpdater do
tag_id: tag.id
}
{:ok, _note_tag} = Notes.create_note_tag(attrs)
case Notes.create_note_tag(attrs) do
{:ok, _note_tag} ->
true
{:error, changeset} ->
Logger.warning(inspect(changeset))
false
end
end
end

View file

@ -17,8 +17,8 @@ defmodule ChiyaWeb.CoreComponents do
alias Phoenix.LiveView.JS
import ChiyaWeb.Gettext
import ChiyaWeb.DarkModeToggle
import Flop.Phoenix
def favicon(assigns) do
~H"""
@ -586,7 +586,9 @@ defmodule ChiyaWeb.CoreComponents do
<dt class="w-1/4 flex-none text-[0.8125rem] leading-6 text-gray-500 dark:text-gray-300">
<%= item.title %>
</dt>
<dd class="text-sm leading-6 text-gray-700 dark:text-gray-400"><%= render_slot(item) %></dd>
<dd class="text-sm leading-6 text-gray-700 dark:text-gray-400 overflow-auto">
<%= render_slot(item) %>
</dd>
</div>
</dl>
</div>
@ -738,6 +740,31 @@ defmodule ChiyaWeb.CoreComponents do
"""
end
attr :meta, Flop.Meta, required: true
attr :id, :string, default: nil
attr :on_change, :string, default: "update-filter"
attr :target, :string, default: nil
attr :fields, :list, default: []
def filter_form(%{meta: meta} = assigns) do
assigns = assign(assigns, form: Phoenix.Component.to_form(meta), meta: nil)
~H"""
<.form
for={@form}
id={@id}
class="stack"
phx-target={@target}
phx-change={@on_change}
phx-submit={@on_change}
>
<.filter_fields :let={i} form={@form} fields={@fields}>
<.input field={i.field} label={i.label} type={i.type} phx-debounce={120} {i.rest} />
</.filter_fields>
</.form>
"""
end
## JS Commands
def show(js \\ %JS{}, selector) do

View file

@ -1,7 +1,6 @@
defmodule ChiyaWeb.Layouts do
use ChiyaWeb, :html
import ChiyaWeb.PublicComponents, only: [divider: 1, site_header: 1]
import PhoenixActiveLink
embed_templates "layouts/*"
end

View file

@ -11,6 +11,11 @@
icon: "hero-speaker-wave-solid",
name: "Channels"
},
%{
path: ~p"/admin/tags",
icon: "hero-document-text-solid",
name: "Tags"
},
%{
path: ~p"/admin/identities",
icon: "hero-user-solid",
@ -40,9 +45,7 @@
</div>
</div>
</header>
<main class="px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl">
<.flash_group flash={@flash} />
<%= @inner_content %>
</div>
<main class="container py-6">
<.flash_group flash={@flash} />
<%= @inner_content %>
</main>

View file

@ -1,2 +1,12 @@
<.flash_group flash={@flash} />
<%= @inner_content %>
<%= if @page_header do %>
<header class="page-header">
<h1><%= @page_title %></h1>
<p><%= Markdown.render(assigns[:content] || "") |> raw %></p>
</header>
<% end %>
<section class="page-grid">
<%= @inner_content %>
</section>

View file

@ -25,6 +25,7 @@
</script>
<script>
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
console.log("Dark Mode")
document.documentElement.dataset["mode"] = "dark"
} else {
delete document.documentElement.dataset["mode"]
@ -34,56 +35,78 @@
<%= @settings.custom_css %>
</style>
</head>
<body class="text-theme-base bg-theme-background | h-feed hfeed">
<aside class="block print:hidden">
<%= raw(@settings.custom_html) %>
</aside>
<header class="my-8 block px-3 print:hidden">
<.site_header user={@current_user} />
<body id="site-body" class="h-feed hfeed">
<header id="site-header">
<nav class="container">
<ul>
<li>
<a href="/" id="site-title">Inhji.de</a>
</li>
<li class="flex-1"></li>
<%= if @current_user do %>
<li>
<a href="/admin">
Admin
</a>
</li>
<% end %>
<li>
<a href="#" id="dark-mode-toggle">
<span class="hidden dark:inline">🌙</span>
<span class="inline dark:hidden">☀️</span>
</a>
</li>
</ul>
</nav>
</header>
<main class="mx-3 md:mx-0">
<%= @inner_content %>
<main id="site-content" class="container print:hidden">
<aside id="primary-sidebar">
<nav class="prose max-w-none">
<div class="menu">
<h3>Pages</h3>
<ul>
<li>
<%= active_link(@conn, "About", to: "/about") %>
</li>
<li>
<%= active_link(@conn, "Wiki", to: "/wiki") %>
</li>
<li>
<%= active_link(@conn, "Bookmarks", to: "/bookmarks") %>
</li>
</ul>
</div>
<div class="menu">
<h3>Channels</h3>
<ul>
<%= for channel <- @channels do %>
<li>
<%= active_link(@conn, channel.name, to: ~p"/channel/#{channel.slug}") %>
</li>
<% end %>
</ul>
</div>
<div class="menu">
<h3>Elsewhere</h3>
<ul>
<%= for identity <- @public_identities do %>
<li><a href={identity.url}><%= identity.name %></a></li>
<% end %>
</ul>
</div>
</nav>
</aside>
<section id="content-wrapper">
<%= @inner_content %>
</section>
</main>
<footer class="max-w-full mt-8 p-8 text-theme-base/75 bg-theme-background1 print:hidden">
<section class="max-w-2xl mx-auto flex gap-3">
<div class="flex-1">
<h2 class="font-bold tracking-wider">Info</h2>
<ul class="list-disc list-inside">
<li>Served by Chiya v<%= Application.spec(:chiya, :vsn) %></li>
<li>Made by Inhji</li>
</ul>
</div>
<div class="flex-1">
<h2 class="font-bold tracking-wider">Links</h2>
<ul class="list-disc list-inside">
<li><a href={~p"/wiki"}>Wiki</a></li>
<li><a href={~p"/about"}>About</a></li>
</ul>
</div>
<div class="flex-1">
<h2 class="font-bold tracking-wider">Elsewhere</h2>
<ul class="list-disc list-inside">
<%= for identity <- @public_identities do %>
<li><a href={identity.url}><%= identity.name %></a></li>
<% end %>
</ul>
</div>
</section>
<section class="max-w-2xl mx-auto">
<.divider />
<div data-dummy="true" />
</section>
<p class="mt-4 max-w-2xl mx-auto text-center">
<footer id="site-footer" class="container">
<p class="text-center">
Struggling to make a decent website since 2011
</p>
</footer>
@ -99,13 +122,5 @@
<span class="p-note"><%= @profile.bio %></span>
</section>
<% end %>
<section class="flex h-1 w-full flex-row">
<div class="bg-theme-primary w-full"></div>
<div class="bg-theme-secondary w-full"></div>
<div class="bg-theme-tertiary w-full"></div>
<div class="bg-theme-quaternary w-full"></div>
<div class="bg-theme-quinary w-full"></div>
</section>
</body>
</html>

View file

@ -6,12 +6,6 @@ defmodule ChiyaWeb.PublicComponents do
router: ChiyaWeb.Router,
statics: ChiyaWeb.static_paths()
import ChiyaWeb.Format
import ChiyaWeb.Markdown, only: [render: 1]
import Phoenix.HTML, only: [raw: 1]
import ChiyaWeb.DarkModeToggle
@doc """
Renders a [Hero Icon](https://heroicons.com).
@ -41,16 +35,6 @@ defmodule ChiyaWeb.PublicComponents do
"""
end
@doc """
Renders a middot as divider
"""
attr :class, :string, default: "text-theme-primary"
def dot(assigns),
do: ~H"""
<span class={["font-bold", @class]}>·</span>
"""
@doc """
Renders a horizontal line
"""
@ -59,39 +43,6 @@ defmodule ChiyaWeb.PublicComponents do
<hr class="my-6 border-theme-base/20" />
"""
attr :text, :string, default: ""
def divider(assigns) do
~H"""
<div class="flex items-center my-8 text-theme-primary before:flex-1 after:flex-1 before:content-[''] after:content-[''] before:p-[0.5px] after:p-[0.5px] before:bg-theme-base/25 after:bg-theme-base/25 w-full mx-auto last:hidden">
<%= assigns.text %>
</div>
"""
end
attr :note, :map, required: true
attr :class_tag, :string, default: ""
attr :linked, :boolean, default: true
def tags(assigns) do
~H"""
<span class="inline-flex flex-row gap-1">
<%= for tag <- @note.tags do %>
<%= if assigns.linked do %>
<a class={["p-category", @class_tag]} href={~p"/tagged-with/#{tag.slug}"}>
<%= tag.name %>
</a>
<% else %>
<span class={["p-category", @class_tag]}>
<%= tag.name %>
</span>
<% end %>
<.dot class="text-theme-base/50 last:hidden" />
<% end %>
</span>
"""
end
@doc """
Renders a note-header with title.
"""
@ -103,224 +54,17 @@ defmodule ChiyaWeb.PublicComponents do
def header(assigns) do
~H"""
<header class={["p-8 rounded bg-theme-background1", @class]}>
<h1 class={["text-3xl leading-10 text-theme-base", @class_title]}>
<header class={[@class]}>
<h1 class={["text-3xl leading-10 font-bold text-theme-base1", @class_title]}>
<%= render_slot(@inner_block) %>
</h1>
<p
:if={@subtitle != []}
class={["mt-4 leading-7 font-semibold text-theme-base/75", @class_subtitle]}
>
<p :if={@subtitle != []} class={["mt-4 leading-7 text-theme-base", @class_subtitle]}>
<%= render_slot(@subtitle) %>
</p>
</header>
"""
end
attr :layout, :atom, default: :list
attr :notes, :list, required: true
attr :show_content, :boolean, default: true
def note_list(assigns) do
case assigns.layout do
:gallery ->
note_list_gallery(assigns)
:microblog ->
note_list_microblog(assigns)
_ ->
note_list_default(assigns)
end
end
attr :notes, :list, required: true
@doc """
Default note list that renders a list of rounded boxes,
which show the note title and an excerpt of the content
"""
def note_list_default(assigns) do
~H"""
<section class="note-list default | sm:w-auto flex flex-col gap-3">
<%= for note <- assigns.notes do %>
<a
href={~p"/note/#{note.slug}"}
class="block rounded-lg px-6 py-4 border border-theme-background1 hover:bg-theme-background1 transition"
>
<header class="flex flex-row items-center gap-1">
<span class="text-theme-primary text-lg font-semibold leading-8 flex-1">
<%= note.name %>
</span>
<span class="text-theme-base/75 text-sm">
<%= pretty_date(note.published_at) %>
</span>
</header>
<%= if assigns.show_content do %>
<p class="text-theme-base">
<%= String.slice(note.content, 0..150) %>
</p>
<% end %>
<%= if not Enum.empty?(note.tags) do %>
<span class="inline-block">
<.tags note={note} linked={false} />
</span>
<% end %>
</a>
<% end %>
</section>
"""
end
attr :notes, :list, required: true
def note_list_microblog(assigns) do
~H"""
<section class="note-list microblog | mt-6 text-theme-base">
<%= for note <- assigns.notes do %>
<article class="mt-4 first:mt-0">
<.featured_images note={note} />
<header class="mt-4 text-lg">
<%= if(note.kind == :bookmark) do %>
<strong><%= note.name %></strong>
<% end %>
</header>
<div class="prose prose-gruvbox mt-4">
<%= raw(render(note.content)) %>
</div>
<footer class="mt-4">
<a href={~p"/note/#{note.slug}"}>
<time class="text-theme-base/75">
<%= pretty_datetime(note.published_at) %>
</time>
</a>
<%= if not Enum.empty?(note.tags) do %>
<.dot />
<.tags note={note} class_tag="text-theme-base/75" />
<% end %>
</footer>
</article>
<.divider />
<% end %>
</section>
"""
end
attr :notes, :list, required: true
def note_list_gallery(assigns) do
~H"""
<section class="note-list gallery | mt-6">
<%= for note <- assigns.notes do %>
<article>
<section class="flex flex-wrap justify-start gap-3">
<%= for image <- note.images do %>
<a
href={ChiyaWeb.Helpers.image_url(image, :full)}
class="lightbox | w-28"
data-gallery={gallery_name(note)}
data-description={ChiyaWeb.Markdown.render(image.content)}
>
<img src={ChiyaWeb.Helpers.image_url(image, :thumb)} loading="lazy" />
</a>
<% end %>
</section>
<a
href={~p"/note/#{note.slug}"}
class="text-theme-secondary text-lg/10 font-semibold rounded-lg -mx-2 -my-0.5 px-2 py-0.5 hover:bg-theme-secondary/10 transition"
>
<%= note.name %>
<span class="text-theme-base/75 text-sm">
<%= pretty_date(note.published_at) %>
</span>
</a>
</article>
<.line />
<% end %>
</section>
"""
end
attr :user, :map, required: true
def site_header(assigns) do
~H"""
<nav class="mx-auto max-w-2xl">
<ul class="flex gap-3">
<li>
<a href="/" class="button">
<.icon name="hero-home" />
</a>
</li>
<li>
<a href="/about" class="button">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
class="bi bi-umbrella inline"
viewBox="0 0 16 16"
>
<path d="M8 0a.5.5 0 0 1 .5.5v.514C12.625 1.238 16 4.22 16 8c0 0 0 .5-.5.5-.149 0-.352-.145-.352-.145l-.004-.004-.025-.023a3.484 3.484 0 0 0-.555-.394A3.166 3.166 0 0 0 13 7.5c-.638 0-1.178.213-1.564.434a3.484 3.484 0 0 0-.555.394l-.025.023-.003.003s-.204.146-.353.146-.352-.145-.352-.145l-.004-.004-.025-.023a3.484 3.484 0 0 0-.555-.394 3.3 3.3 0 0 0-1.064-.39V13.5H8h.5v.039l-.005.083a2.958 2.958 0 0 1-.298 1.102 2.257 2.257 0 0 1-.763.88C7.06 15.851 6.587 16 6 16s-1.061-.148-1.434-.396a2.255 2.255 0 0 1-.763-.88 2.958 2.958 0 0 1-.302-1.185v-.025l-.001-.009v-.003s0-.002.5-.002h-.5V13a.5.5 0 0 1 1 0v.506l.003.044a1.958 1.958 0 0 0 .195.726c.095.191.23.367.423.495.19.127.466.229.879.229s.689-.102.879-.229c.193-.128.328-.304.424-.495a1.958 1.958 0 0 0 .197-.77V7.544a3.3 3.3 0 0 0-1.064.39 3.482 3.482 0 0 0-.58.417l-.004.004S5.65 8.5 5.5 8.5c-.149 0-.352-.145-.352-.145l-.004-.004a3.482 3.482 0 0 0-.58-.417A3.166 3.166 0 0 0 3 7.5c-.638 0-1.177.213-1.564.434a3.482 3.482 0 0 0-.58.417l-.004.004S.65 8.5.5 8.5C0 8.5 0 8 0 8c0-3.78 3.375-6.762 7.5-6.986V.5A.5.5 0 0 1 8 0zM6.577 2.123c-2.833.5-4.99 2.458-5.474 4.854A4.124 4.124 0 0 1 3 6.5c.806 0 1.48.25 1.962.511a9.706 9.706 0 0 1 .344-2.358c.242-.868.64-1.765 1.271-2.53zm-.615 4.93A4.16 4.16 0 0 1 8 6.5a4.16 4.16 0 0 1 2.038.553 8.688 8.688 0 0 0-.307-2.13C9.434 3.858 8.898 2.83 8 2.117c-.898.712-1.434 1.74-1.731 2.804a8.687 8.687 0 0 0-.307 2.131zm3.46-4.93c.631.765 1.03 1.662 1.272 2.53.233.833.328 1.66.344 2.358A4.14 4.14 0 0 1 13 6.5c.77 0 1.42.23 1.897.477-.484-2.396-2.641-4.355-5.474-4.854z" />
</svg>
<span class="hidden sm:inline-block">About</span>
</a>
</li>
<li>
<a href="/wiki" class="button">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
class="bi bi-journals inline"
viewBox="0 0 16 16"
>
<path d="M5 0h8a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2 2 2 0 0 1-2 2H3a2 2 0 0 1-2-2h1a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1H1a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v9a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1H3a2 2 0 0 1 2-2z" />
<path d="M1 6v-.5a.5.5 0 0 1 1 0V6h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0V9h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 2.5v.5H.5a.5.5 0 0 0 0 1h2a.5.5 0 0 0 0-1H2v-.5a.5.5 0 0 0-1 0z" />
</svg>
<span class="hidden sm:inline">Wiki</span>
</a>
</li>
<li>
<a href="/bookmarks" class="button">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
class="bi bi-bookmark-fill inline"
viewBox="0 0 16 16"
>
<path d="M2 2v13.5a.5.5 0 0 0 .74.439L8 13.069l5.26 2.87A.5.5 0 0 0 14 15.5V2a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2z" />
</svg>
<span class="hidden sm:inline-block">Bookmarks</span>
</a>
</li>
<li class="flex-1"></li>
<%= if @user do %>
<li>
<a href="/admin" class="button">
<.icon name="hero-beaker" />
</a>
</li>
<% end %>
<li>
<.darkmode_toggle class="button" />
</li>
</ul>
</nav>
"""
end
attr :note, :map, required: true
def featured_images(assigns) do
@ -336,7 +80,7 @@ defmodule ChiyaWeb.PublicComponents do
assigns = assign(assigns, :image, List.first(images))
~H"""
<figure>
<figure class="images-1">
<.featured_image image={assigns.image} size={:full} class="rounded-lg" />
</figure>
"""
@ -348,9 +92,9 @@ defmodule ChiyaWeb.PublicComponents do
|> assign(:second, Enum.at(images, 1))
~H"""
<figure class="flex gap-1">
<.featured_image image={assigns.first} size={:thumb} class="rounded-l flex-1 w-full" />
<.featured_image image={assigns.second} size={:thumb} class="rounded-r flex-1 w-full" />
<figure class="images-2 | flex gap-1">
<.featured_image image={assigns.first} size={:full} class="rounded-l flex-1 w-full" />
<.featured_image image={assigns.second} size={:full} class="rounded-r flex-1 w-full" />
</figure>
"""
@ -362,7 +106,7 @@ defmodule ChiyaWeb.PublicComponents do
|> assign(:third, Enum.at(images, 2))
~H"""
<figure class="flex gap-1">
<figure class="images-3 | flex gap-1">
<.featured_image image={assigns.first} size={:thumb} class="flex-1 w-full rounded-l" />
<.featured_image image={assigns.second} size={:thumb} class="flex-1 w-full" />
<.featured_image image={assigns.third} size={:thumb} class="flex-1 w-full rounded-r" />
@ -378,7 +122,7 @@ defmodule ChiyaWeb.PublicComponents do
|> assign(:fourth, Enum.at(images, 3))
~H"""
<figure class="flex gap-1 flex-col">
<figure class="images-4 | flex gap-1 flex-col">
<section class="flex gap-1">
<.featured_image image={assigns.first} size={:thumb} class="flex-1 w-full rounded-tl-lg" />
<.featured_image image={assigns.second} size={:thumb} class="flex-1 w-full rounded-tr-lg" />
@ -402,12 +146,11 @@ defmodule ChiyaWeb.PublicComponents do
src={ChiyaWeb.Helpers.image_url(assigns.image, assigns.size)}
class={assigns.class}
title={assigns.image.content}
loading="lazy"
/>
"""
end
defp gallery_name(note), do: "gallery-#{note.id}"
defp main_images(note),
do: Enum.filter(note.images, fn image -> image.featured end)
end

View file

@ -5,29 +5,6 @@ defmodule ChiyaWeb.NoteController do
alias Chiya.Notes
alias Chiya.Notes.{Note, NoteImport}
def index(conn, %{"channel" => channel_slug}) do
channel = Chiya.Channels.get_channel_by_slug!(channel_slug)
notes = Notes.list_notes_by_channel(channel)
conn
|> with_channels()
|> render(:index,
notes: notes,
page_title: "Notes"
)
end
def index(conn, _params) do
notes = Notes.list_notes()
conn
|> with_channels()
|> render(:index,
notes: notes,
page_title: "Notes"
)
end
def new(conn, _params) do
default_channels = get_default_channels(conn)
@ -54,8 +31,6 @@ defmodule ChiyaWeb.NoteController do
|> put_flash(:info, "Note created successfully.")
|> redirect(to: ~p"/admin/notes/#{note}")
# TODO: set channels from changeset when error happened?
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :new,
changeset: changeset,
@ -67,10 +42,6 @@ defmodule ChiyaWeb.NoteController do
end
end
def show(conn, %{"id" => id}) do
live_render(conn, NoteShowLive, session: %{"note_id" => id})
end
def edit(conn, %{"id" => id}) do
note = Notes.get_note_preloaded!(id)
changeset = Notes.change_note(note)
@ -96,8 +67,6 @@ defmodule ChiyaWeb.NoteController do
|> put_flash(:info, "Note updated successfully.")
|> redirect(to: ~p"/admin/notes/#{note}")
# TODO: set channels from changeset when error happened?
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :edit,
note: note,
@ -222,17 +191,7 @@ defmodule ChiyaWeb.NoteController do
)
end
defp with_channels(conn) do
assign(
conn,
:channels,
Chiya.Channels.list_channels()
|> Chiya.Channels.preload_channel()
|> Enum.filter(fn c -> not Enum.empty?(c.notes) end)
)
end
defp get_default_channels(conn = %Plug.Conn{}) do
defp get_default_channels(%Plug.Conn{} = conn) do
if conn.assigns.settings.default_channel do
[conn.assigns.settings.default_channel.id]
else

View file

@ -13,15 +13,19 @@
<section class="flex flex-row flex-wrap mt-6 -mb-6 gap-3">
<a
href={~p"/admin/notes"}
href="#"
class="text-sm dark:text-gray-300 rounded-full bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 px-4 py-2 border border-gray-300 dark:border-gray-600 shadow-sm transition"
phx-click="update-channel"
phx-value-channelid={nil}
>
All <span class="text-gray-600 dark:text-gray-500">(<%= Enum.count(@notes) %>)</span>
</a>
<%= for channel <- @channels do %>
<a
href={~p"/admin/notes?channel=#{channel.slug}"}
href="#"
class="text-sm dark:text-gray-300 rounded-full bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 px-4 py-2 border border-gray-300 dark:border-gray-600 shadow-sm transition"
phx-click="update-channel"
phx-value-channelid={channel.id}
>
<%= channel.name %>
<span class="text-gray-600 dark:text-gray-500">(<%= Enum.count(channel.notes) %>)</span>
@ -34,3 +38,15 @@
<:col :let={note} label="Updated at"><%= from_now(note.updated_at) %></:col>
<:col :let={note} label="Published at"><%= from_now(note.published_at) %></:col>
</.table>
<.filter_form fields={[:name]} meta={@meta} id="user-filter-form" />
<Flop.Phoenix.table items={@notes} meta={@meta} path={~p"/admin/notes"}>
<:col :let={note} label="Name" field={:name}><%= note.name %></:col>
<:col :let={note} label="Updated at" field={:updated_at}><%= from_now(note.updated_at) %></:col>
<:col :let={note} label="Published at" field={:published_at}>
<%= from_now(note.published_at) %>
</:col>
</Flop.Phoenix.table>
<Flop.Phoenix.pagination meta={@meta} path={~p"/admin/notes"} />

View file

@ -1,31 +1,37 @@
defmodule ChiyaWeb.PageController do
use ChiyaWeb, :controller
alias Chiya.Channels
plug :put_layout, html: {ChiyaWeb.Layouts, :public}
plug :put_assigns
def home(conn, _params) do
settings = conn.assigns.settings
def home(conn, params) do
if id = conn.assigns.settings.home_channel_id do
channel = Channels.get_channel!(id)
{:ok, {notes, meta}} = Chiya.Notes.list_home_notes(channel, params)
channel =
case settings.home_channel_id do
nil -> nil
id -> Chiya.Channels.get_channel!(id) |> Chiya.Channels.preload_channel_public()
end
render(conn, :home,
channel: channel,
page_title: "Home"
)
render(conn, :home,
channel: channel,
notes: notes,
meta: meta,
page_title: "Home",
page_header: false
)
else
not_found(conn)
end
end
def channel(conn, %{"slug" => channel_slug}) do
channel =
Chiya.Channels.get_channel_by_slug!(channel_slug)
|> Chiya.Channels.preload_channel_public()
channel_slug
|> Channels.get_channel_by_slug!()
|> Channels.preload_channel_public()
render(conn, :channel,
channel: channel,
page_title: channel.name
page_title: channel.name,
content: channel.content
)
end
@ -34,7 +40,7 @@ defmodule ChiyaWeb.PageController do
render(conn, :tag,
tag: tag,
page_title: tag.name
page_title: "Tagged with #{tag.name}"
)
end
@ -42,14 +48,15 @@ defmodule ChiyaWeb.PageController do
note = Chiya.Notes.get_note_by_slug_preloaded!(note_slug)
changeset = Chiya.Notes.change_note_comment(%Chiya.Notes.NoteComment{}, %{note_id: note.id})
if is_nil(note.published_at) and is_nil(conn.assigns.current_user) do
render_error(conn, :not_found)
else
if note.published_at || conn.assigns.current_user do
render(conn, :note,
note: note,
changeset: changeset,
page_title: note.name,
changeset: changeset
page_header: false
)
else
not_found(conn)
end
end
@ -57,66 +64,56 @@ defmodule ChiyaWeb.PageController do
note = Chiya.Notes.get_note_by_slug_preloaded("about")
user = Chiya.Accounts.get_user!(1)
render(conn, :about,
note: note,
user: user,
page_title: "About"
)
if note && user do
render(conn, :about,
note: note,
user: user,
page_title: user.name
)
else
not_found(conn)
end
end
def wiki(conn, _params) do
[channel, notes_updated, notes_published] =
case conn.assigns.settings.wiki_channel_id do
nil ->
[nil, nil, nil]
if id = conn.assigns.settings.wiki_channel_id do
channel = Chiya.Channels.get_channel!(id)
notes = Chiya.Notes.list_notes_by_channel_updated(channel, 999)
id ->
channel = Chiya.Channels.get_channel!(id)
updated = Chiya.Notes.list_notes_by_channel_updated(channel, 5)
published = Chiya.Notes.list_notes_by_channel_published(channel, 5)
[channel, updated, published]
end
render(conn, :wiki,
channel: channel,
notes_updated: notes_updated,
notes_published: notes_published,
page_title: "Wiki"
)
render(conn, :wiki,
channel: channel,
notes: notes,
page_title: channel.name,
content: channel.content
)
else
not_found(conn)
end
end
def bookmarks(conn, _params) do
[channel, notes, tags] =
case conn.assigns.settings.bookmark_channel_id do
nil ->
[nil, nil]
if id = conn.assigns.settings.bookmark_channel_id do
channel = Chiya.Channels.get_channel!(id)
notes = Chiya.Notes.list_notes_by_channel_published(channel, 999)
id ->
channel = Chiya.Channels.get_channel!(id)
notes = Chiya.Notes.list_notes_by_channel_published(channel, 999)
tags = group_tags(notes)
[channel, notes, tags]
end
render(conn, :bookmarks,
channel: channel,
notes: notes,
tags: tags,
page_title: "Bookmarks"
)
render(conn, :bookmarks,
channel: channel,
notes: notes,
page_title: "#{Enum.count(notes)} Bookmarks"
)
else
not_found(conn)
end
end
defp group_tags(notes) do
Enum.reduce(notes, [], fn n, acc ->
acc ++ n.tags
end)
|> Enum.uniq_by(fn t -> t.id end)
|> Enum.sort_by(fn t -> t.slug end, :asc)
|> Enum.group_by(
fn n -> String.first(n.name) end,
fn n -> n end
)
|> IO.inspect()
defp not_found(conn) do
conn
|> assign(:page_title, "404 Not found")
|> render_error(:not_found)
end
defp put_assigns(conn, opts) do
conn
|> assign(:page_header, true)
end
end

View file

@ -1,36 +1,47 @@
defmodule ChiyaWeb.PageHTML do
use ChiyaWeb, :html_public
import Phoenix.HTML.Tag, only: [content_tag: 3, content_tag: 2]
import ChiyaWeb.Format, only: [pretty_datetime: 1, pretty_date: 1]
embed_templates "page_html/*"
def tag_list([]), do: "No Tags"
def tag_list(tags), do: Enum.map_join(tags, ", ", fn t -> t.name end)
attr :notes, :list, required: true
attr :layout, :atom, default: :default
attr :show_content, :boolean, default: true
def note_list(assigns)
attr :notes, :list, required: true
attr :show_content, :boolean, default: true
def note_list_default(assigns)
attr :notes, :list, required: true
attr :show_content, :boolean, default: true
def note_list_microblog(assigns)
attr :notes, :list, required: true
attr :show_content, :boolean, default: true
def note_list_gallery(assigns)
attr :note, :map, required: true
attr :linked, :boolean, default: true
def tag_list(assigns)
def render_outline(note) do
note.content
|> ChiyaWeb.Outline.get()
|> Enum.map(&do_render_outline/1)
|> Enum.map(&safe_to_string/1)
ChiyaWeb.OutlineRenderer.render_outline(note.content)
end
def has_outline?(note) do
outline_empty =
note.content
|> ChiyaWeb.Outline.get()
|> Enum.empty?()
!outline_empty
ChiyaWeb.OutlineRenderer.has_outline?(note.content)
end
def do_render_outline(%{text: text, children: children, level: _level}) do
slug = Slugger.slugify_downcase(text)
content_tag(:ul, [class: "m-0"],
do: [
content_tag(:li, do: content_tag(:a, text, href: "##{slug}")),
Enum.map(children, &do_render_outline/1)
]
def group_tags(notes) do
Enum.reduce(notes, [], fn n, acc ->
acc ++ n.tags
end)
|> Enum.uniq_by(fn t -> t.id end)
|> Enum.sort_by(fn t -> t.slug end, :asc)
|> Enum.group_by(
fn n -> String.first(n.name) end,
fn n -> n end
)
end
end

View file

@ -1,20 +1,23 @@
<section class="max-w-2xl mx-auto">
<article class="h-card hcard">
<section class="flex gap-3">
<img
class="rounded-lg block text-center w-28 h-28 | u-photo"
src={ChiyaWeb.Uploaders.UserImage.url({@user.user_image, @current_user}, :thumb)}
/>
<.header class="flex-1">
<span class="p-name"><%= @user.name %></span>
<:subtitle><%= @user.bio %></:subtitle>
</.header>
</section>
<section>
<section class="flex gap-3">
<img
class="rounded-lg block text-center w-28 h-28 | u-photo"
src={ChiyaWeb.Uploaders.UserImage.url({@user.user_image, @current_user}, :thumb)}
/>
<p><%= @user.bio %></p>
</section>
<%= if @note do %>
<section class="mx-auto mt-8 prose prose-gruvbox md:prose-lg lg:prose-xl | p-summary e-content">
<%= Markdown.render(@note.content) |> raw %>
</section>
<% end %>
</article>
<section class="prose max-w-none | p-summary e-content">
<%= Markdown.render(@note.content) |> raw %>
</section>
</section>
<aside class="prose max-w-none">
<%= if has_outline?(@note) do %>
<h3>Outline</h3>
<section>
<%= raw(render_outline(@note)) %>
</section>
<% end %>
</aside>

View file

@ -1,45 +1,7 @@
<%= if @channel do %>
<section class="mx-auto max-w-2xl">
<.header>
Bookmarks
<:subtitle><%= Enum.count(@notes) in total %></:subtitle>
</.header>
</section>
<section>
<.note_list notes={@notes} layout={@channel.layout} />
</section>
<section class="mt-6 grid grid-cols-1 md:grid-cols-3 gap-3 max-w-4xl mx-auto">
<section class="col-span-1 md:col-span-2">
<.note_list notes={@notes} layout={@channel.layout} show_content={false} />
</section>
<section>
<ul>
<%= for {letter, tag_group} <- @tags do %>
<li class="mb-2">
<span class="capitalize text-theme-primary border border-theme-background1 rounded font-sm px-2 py-1 inline-block mb-2">
<%= letter %>
</span>
<%= for tag <- tag_group do %>
<a
href={~p"/tagged-with/#{tag.slug}"}
class="border border-theme-background1 rounded font-sm px-2 py-1 inline-block mb-2 hover:bg-theme-background1 transition"
>
<%= tag.name %> <%= Enum.count(tag.notes) %>
</a>
<% end %>
</li>
<% end %>
</ul>
</section>
</section>
<% else %>
<section class="mx-auto max-w-2xl">
<.header>
Bookmarks
</.header>
<section class="prose prose-gruvbox mt-6">
Bookmarks not set up.
</section>
</section>
<% end %>
<aside>
<.tag_bar notes={@notes} />
</aside>

View file

@ -1,10 +1,7 @@
<section class="max-w-2xl mx-auto">
<.header>
<%= @channel.name %>
<:subtitle><%= Markdown.render(@channel.content) |> raw %></:subtitle>
</.header>
<div class="w-full mt-6 sm:w-auto flex flex-col gap-1.5">
<.note_list notes={@channel.notes} layout={@channel.layout} />
</div>
<section>
<.note_list notes={@channel.notes} layout={@channel.layout} />
</section>
<aside>
<.tag_bar notes={@channel.notes} />
</aside>

View file

@ -1,29 +1,5 @@
<section class="max-w-2xl mx-auto">
<.header class_title="text-theme-primary p-name" class_subtitle="p-summary">
<%= @settings.title %>
<:subtitle><%= @settings.subtitle %></:subtitle>
</.header>
<section>
<.note_list notes={@notes} layout={@channel.layout} />
<section class="text-sm my-8">
<ul class="flex flex-wrap gap-3">
<li>
<a href="#" class="button">
<.icon name="hero-megaphone" />
</a>
</li>
<%= for channel <- @channels do %>
<li>
<a href={~p"/channel/#{channel.slug}"} class="button">
<%= channel.name %>
</a>
</li>
<% end %>
</ul>
</section>
<%= if @channel do %>
<section class="mt-8">
<.note_list notes={@channel.notes} layout={@channel.layout} />
</section>
<% end %>
<Flop.Phoenix.pagination meta={@meta} path={~p"/"} />
</section>

View file

@ -1,41 +1,90 @@
<article class="h-entry hentry">
<.header class="max-w-2xl mx-auto" class_title="p-name">
<%= @note.name %>
</.header>
<section>
<article class="h-entry hentry note | stack container">
<header class="prose max-w-none">
<h1 class="p-name">
<%= @note.name %>
</h1>
</header>
<%= if @current_user do %>
<section class="max-w-2xl mx-auto mt-8 print:hidden">
<ul class="flex gap-3">
<li>
<a href={~p"/admin/notes/#{@note}/edit"} class="button">
<.icon name="hero-pencil-square" /> Edit
</a>
</li>
<li><a href={~p"/admin/notes/#{@note}"} class="button">Show in Admin</a></li>
<aside>
<.featured_images note={@note} />
</aside>
<section class="p-summary e-content | prose max-w-none">
<%= Markdown.render(@note.content) |> raw %>
</section>
<footer>
<a href={~p"/"} class="hidden | h-card u-author">Inhji</a>
<a href={~p"/note/#{@note.slug}"} class="hidden | u-url u-uid"><%= @note.name %></a>
</footer>
<section>
<%= if !Enum.empty?(@note.images) do %>
<div class="flex flex-wrap gap-3">
<%= for image <- @note.images do %>
<a
href={ChiyaWeb.Uploaders.NoteImage.url({image.path, image}, :full_dithered)}
class="lightbox | w-28"
data-gallery="note"
data-description={ChiyaWeb.Markdown.render(image.content)}
>
<img
src={ChiyaWeb.Uploaders.NoteImage.url({image.path, image}, :thumb_dithered)}
class="rounded"
/>
</a>
<% end %>
</div>
<% end %>
</section>
</article>
</section>
<aside class="prose max-w-none">
<%= if has_outline?(@note) do %>
<h3><.icon name="hero-rectangle-stack" /> Outline</h3>
<section>
<%= raw(render_outline(@note)) %>
</section>
<% end %>
<h3><.icon name="hero-rectangle-group" /> Properties</h3>
<section>
<ul>
<li>
<%= if @note.published_at do %>
<strong>Published</strong>
<% else %>
<strong>Unpublished</strong>
<% end %>
<time class="dt-published"><%= pretty_date(@note.published_at) %></time>
</li>
<li>
<strong>Last updated</strong>
<time class="dt-published"><%= pretty_date(@note.published_at) %></time>
</li>
<li><strong>Tags</strong> <.tag_list note={@note} /></li>
<li><strong>Kind</strong> <%= @note.kind %></li>
</ul>
</section>
<%= if String.length(@note.url || "") > 0 do %>
<h3><.icon name="hero-link" /> URL</h3>
<section>
<ul>
<li><a href={@note.url}><%= @note.url %></a></li>
</ul>
</section>
<% end %>
<aside class="max-w-2xl mx-auto mt-8">
<.featured_images note={@note} />
</aside>
<%= if has_outline?(@note) do %>
<aside class="max-w-2xl mx-auto mt-8 prose prose-gruvbox print:hidden">
<div class="bg-theme-background1 rounded p-2">
<%= raw(render_outline(@note)) %>
</div>
</aside>
<% end %>
<section class="mt-8 mx-auto prose prose-gruvbox md:prose-lg lg:prose-xl | p-summary e-content">
<%= Markdown.render(@note.content) |> raw %>
</section>
<%= if not Enum.empty?(@note.links_to) do %>
<section class="mt-8 prose prose-gruvbox max-w-2xl mx-auto">
<.divider text="" /> Notes linking here:
<ul class="">
<h3><.icon name="hero-arrow-down-on-square" /> Notes linking here</h3>
<section>
<ul>
<%= for link <- @note.links_to do %>
<li><a href={~p"/note/#{link.slug}"}><%= link.name %></a></li>
<% end %>
@ -43,57 +92,20 @@
</section>
<% end %>
<%= if String.length(@note.url || "") > 0 do %>
<section class="mt-8 max-w-2xl mx-auto text-center text-lg">
<.divider text=" " />
<.icon name="hero-link" /> <a href={@note.url}><%= @note.url %></a>
</section>
<% end %>
<h3><.icon name="hero-wrench-screwdriver" /> Admin</h3>
<footer class="max-w-2xl mx-auto mt-8 text-theme-base">
<%= if @note.published_at do %>
<span>Published</span>
<% else %>
<span>Unpublished</span>
<% end %>
<time class="font-semibold | dt-published"><%= pretty_date(@note.published_at) %></time>
<.dot />
<span>Last Updated</span>
<time class="font-semibold" datetime={datetime(@note.updated_at)}>
<%= pretty_date(@note.updated_at) %>
</time>
<%= if not Enum.empty?(@note.tags) do %>
<.dot />
<.tags note={@note} />
<% end %>
<%= if @note.kind != :post do %>
<.dot />
<span><%= @note.kind %></span>
<% end %>
<a href={~p"/"} class="hidden | h-card u-author">Inhji</a>
<a href={~p"/note/#{@note.slug}"} class="hidden | u-url u-uid"><%= @note.name %></a>
</footer>
<section class="max-w-2xl mx-auto mt-8">
<%= if !Enum.empty?(@note.images) do %>
<.line />
<div class="flex flex-wrap gap-3">
<%= for image <- @note.images do %>
<a
href={ChiyaWeb.Uploaders.NoteImage.url({image.path, image}, :full_dithered)}
class="lightbox | w-28"
data-gallery="note"
data-description={ChiyaWeb.Markdown.render(image.content)}
>
<img
src={ChiyaWeb.Uploaders.NoteImage.url({image.path, image}, :thumb_dithered)}
class="rounded"
/>
</a>
<% end %>
</div>
<% end %>
<section>
<ul>
<li>
<a href={~p"/admin/notes/#{@note}/edit"} class="button">
Edit
</a>
</li>
<li>
<a href={~p"/admin/notes/#{@note}"} class="button">
Show in Admin
</a>
</li>
</ul>
</section>
</article>
</aside>

View file

@ -0,0 +1,9 @@
<%= if @layout == :default do %>
<.note_list_default notes={@notes} show_content={@show_content} />
<% end %>
<%= if @layout == :gallery do %>
<.note_list_gallery notes={@notes} show_content={@show_content} />
<% end %>
<%= if @layout == :microblog do %>
<.note_list_microblog notes={@notes} show_content={@show_content} />
<% end %>

View file

@ -0,0 +1,30 @@
<section class="note-list default stack">
<%= for note <- assigns.notes do %>
<article class="prose max-w-none">
<header class="flex flex-row items-center gap-1">
<span class="text-theme-primary text-lg font-semibold leading-8 flex-1">
<a href={~p"/note/#{note.slug}"}>
<%= note.name %>
</a>
</span>
</header>
<%= if assigns.show_content do %>
<p class="text-theme-base mb-3">
<%= Chiya.Notes.Note.note_excerpt(note.content) %>
</p>
<% end %>
<footer class="flex">
<span class="text-theme-base/75 flex-1">
<%= pretty_date(note.published_at) %>
</span>
<%= if not Enum.empty?(note.tags) do %>
<span class="inline-block">
<.tag_list note={note} linked={false} />
</span>
<% end %>
</footer>
</article>
<% end %>
</section>

View file

@ -0,0 +1,29 @@
<section class="note-list gallery | stack">
<%= for note <- assigns.notes do %>
<article>
<section class="flex flex-wrap justify-start gap-3">
<%= for image <- note.images do %>
<a
href={ChiyaWeb.Helpers.image_url(image, :full)}
class="lightbox | w-28"
data-gallery={"gallery-#{note.id}"}
data-description={ChiyaWeb.Markdown.render(image.content)}
>
<img src={ChiyaWeb.Helpers.image_url(image, :thumb)} loading="lazy" />
</a>
<% end %>
</section>
<a
href={~p"/note/#{note.slug}"}
class="text-theme-secondary text-lg/10 font-semibold rounded-lg -mx-2 -my-0.5 px-2 py-0.5 hover:bg-theme-secondary/10 transition"
>
<%= note.name %>
<span class="text-theme-base/75 text-sm">
<%= pretty_date(note.published_at) %>
</span>
</a>
</article>
<.line />
<% end %>
</section>

View file

@ -0,0 +1,31 @@
<section class="note-list microblog | stack">
<%= for note <- assigns.notes do %>
<article class="stack">
<.featured_images note={note} />
<header class="text-lg">
<%= if(note.kind == :bookmark) do %>
<strong><%= note.name %></strong>
<% end %>
</header>
<div class="prose max-w-none">
<%= raw(Markdown.render(note.content)) %>
</div>
<footer class="prose max-w-none">
<a href={~p"/note/#{note.slug}"}>
<time>
<%= pretty_datetime(note.published_at) %>
</time>
</a>
<%= if not Enum.empty?(note.tags) do %>
<span class="dot" />
<.tag_list note={note} />
<% end %>
</footer>
</article>
<div class="divider">⌘</div>
<% end %>
</section>

View file

@ -1,10 +1,7 @@
<section class="max-w-2xl mx-auto">
<.header>
Tagged with &ldquo;<%= @tag.name %>&rdquo;
<:subtitle><%= @tag.content %></:subtitle>
</.header>
<div class="w-full mt-6 sm:w-auto flex flex-col gap-1.5">
<.note_list notes={@tag.notes} />
</div>
<section class="col-span-1 md:col-span-2">
<.note_list notes={@tag.notes} />
</section>
<aside>
<.tag_bar notes={@tag.notes} />
</aside>

View file

@ -0,0 +1,18 @@
<ul class="tag-bar">
<%= for {letter, tag_group} <- group_tags(@notes) do %>
<li>
<span class="letter">
<%= letter %>
</span>
<%= for tag <- tag_group do %>
<a href={~p"/tagged-with/#{tag.slug}"}>
<%= tag.name %>
<span class="count">
<%= Enum.count(tag.notes) %>
</span>
</a>
<% end %>
</li>
<% end %>
</ul>

View file

@ -0,0 +1,20 @@
<span class="tags | inline-flex flex-row gap-1 whitespace-normal">
<%= for tag <- @note.tags do %>
<%= if @linked do %>
<a class="tag | flex gap-0" href={~p"/tagged-with/#{tag.slug}"}>
<span>#</span>
<span class="p-category">
<%= tag.name %>
</span>
</a>
<% else %>
<span class="tag | flex gap-0">
<span>#</span>
<span class="p-category">
<%= tag.name %>
</span>
</span>
<% end %>
<span class="dot last:hidden" />
<% end %>
</span>

View file

@ -1,37 +1,7 @@
<%= if @channel do %>
<section class="mx-auto max-w-2xl">
<.header>
<%= @channel.name %>
</.header>
</section>
<section>
<.note_list notes={@notes} />
</section>
<section class="mt-6 flex flex-col gap-3 max-w-2xl mx-auto">
<section class="prose prose-gruvbox md:prose-lg lg:prose-xl max-w-none">
<%= Markdown.render(@channel.content) |> raw %>
</section>
<aside class="flex flex-col md:flex-row gap-3 mt-6">
<div class="mt-6 flex flex-1 flex-col gap-1.5">
<h2 class="text-xl text-theme-base">Recently Updated</h2>
<.note_list notes={@notes_updated} layout={@channel.layout} show_content={false} />
</div>
<div class="mt-6 flex flex-1 flex-col gap-1.5">
<h2 class="text-xl text-theme-base">Recently Published</h2>
<.note_list notes={@notes_published} layout={@channel.layout} show_content={false} />
</div>
</aside>
</section>
<% else %>
<section class="mx-auto max-w-2xl">
<.header>
Wiki
</.header>
<section class="prose prose-gruvbox mt-6">
Wiki is not set up.
</section>
</section>
<% end %>
<aside>
<.tag_bar notes={@notes} />
</aside>

View file

@ -0,0 +1,74 @@
defmodule ChiyaWeb.TagController do
use ChiyaWeb, :controller
alias Chiya.Tags
def index(conn, _params) do
tags = Chiya.Tags.list_admin_tags()
render(conn, :index, tags: tags)
end
def show(conn, %{"id" => id}) do
tag = Tags.get_tag!(id)
render(conn, :show, tag: tag)
end
def apply_prepare(conn, %{"id" => id}) do
tag = Tags.get_tag!(id)
notes =
tag
|> Chiya.Notes.list_apply_notes()
|> Enum.filter(fn note ->
exists =
note.tags
|> Enum.map(fn t -> t.name end)
|> Enum.member?(tag.name)
!exists
end)
render(conn, :apply_prepare, tag: tag, notes: notes)
end
def apply_run(conn, %{"id" => id}) do
tag = Tags.get_tag!(id)
notes = Chiya.Notes.list_apply_notes(tag)
Enum.each(notes, fn note ->
Chiya.Tags.TagUpdater.add_tags(note, [tag.slug])
end)
notes = Chiya.Notes.list_apply_notes(tag)
render(conn, :apply_prepare, tag: tag, notes: notes)
end
def edit(conn, %{"id" => id}) do
tag = Tags.get_tag!(id)
changeset = Tags.change_tag(tag)
render(conn, :edit,
tag: tag,
changeset: changeset,
page_title: "EDIT #{tag.name}"
)
end
def update(conn, %{"id" => id, "tag" => tag_params}) do
tag = Tags.get_tag!(id)
case Tags.update_tag(tag, tag_params) do
{:ok, _tag} ->
conn
|> put_flash(:info, "Tags updated successfully.")
|> redirect(to: ~p"/admin/tags")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :edit,
tag: tag,
changeset: changeset,
page_title: "EDIT #{tag.name}"
)
end
end
end

View file

@ -0,0 +1,5 @@
defmodule ChiyaWeb.TagHTML do
use ChiyaWeb, :html
embed_templates "tag_html/*"
end

View file

@ -0,0 +1,25 @@
<.header>
Apply Tag <%= @tag.name %>
<:subtitle><%= Enum.count(@notes) %> notes will get the tag.</:subtitle>
<:actions>
<.link href={~p"/admin/tags/#{@tag}/apply/run"}>
<.button>Apply tag</.button>
</.link>
</:actions>
</.header>
<section class="card-list">
<%= for note <- @notes do %>
<article class="card">
<header>
<h2>
<a href={"/admin/notes/#{note.id}"}><%= note.name %></a>
</h2>
</header>
<footer>
<span>Updated <%= from_now(note.updated_at) %></span>
<span>Published <%= from_now(note.published_at) %></span>
</footer>
</article>
<% end %>
</section>

View file

@ -0,0 +1,10 @@
<.header>
Edit Tag “<%= @tag.name %>”
<:actions>
<.link href={~p"/admin/tags"}>
<.button><.icon name="hero-arrow-left" /> Back to Tags</.button>
</.link>
</:actions>
</.header>
<.tag_form changeset={@changeset} action={~p"/admin/tags/#{@tag}"} />

View file

@ -0,0 +1,46 @@
<.header>
<.icon name="hero-document-text" /> Tags
<:subtitle>Tags are tags.</:subtitle>
</.header>
<section class="card-list mt-8">
<h3 class="leading-normal text-xl dark:text-gray-100">With Regex</h3>
<%= for tag <- Enum.filter(@tags, fn t -> !!t.regex end) do %>
<article class="card">
<header>
<h2>
<a href={"/admin/tags/#{tag.id}"}><%= tag.name %></a>
</h2>
</header>
<footer>
<span><%= Enum.count(tag.notes) %> notes</span>
<span>Updated <%= from_now(tag.updated_at) %></span>
<%= if String.length(tag.regex || "") > 0 do %>
<span>Regex: <%= tag.regex %></span>
<% end %>
</footer>
</article>
<% end %>
</section>
<section class="card-list mt-8">
<h3 class="leading-normal text-xl dark:text-gray-100">Without Regex</h3>
<%= for tag <- Enum.filter(@tags, fn t -> !t.regex end) do %>
<article class="card">
<header>
<h2>
<a href={"/admin/tags/#{tag.id}"}><%= tag.name %></a>
</h2>
</header>
<footer>
<span><%= Enum.count(tag.notes) %> notes</span>
<span>Updated <%= from_now(tag.updated_at) %></span>
<%= if String.length(tag.regex || "") > 0 do %>
<span>Regex: <%= tag.regex %></span>
<% end %>
</footer>
</article>
<% end %>
</section>

View file

@ -0,0 +1,21 @@
<.header>
Tag <%= @tag.id %>
<:subtitle>This is a tag record from your database.</:subtitle>
<:actions>
<.link href={~p"/admin/tags/#{@tag}/edit"}>
<.button>Edit tag</.button>
</.link>
<.link href={~p"/admin/tags/#{@tag}/apply"}>
<.button>Apply tag</.button>
</.link>
</:actions>
</.header>
<.list>
<:item title="Name"><%= @tag.name %></:item>
<:item title="Content"><%= @tag.content %></:item>
<:item title="Slug"><%= @tag.slug %></:item>
<:item title="Regex"><%= @tag.regex %></:item>
</.list>
<.back navigate={~p"/admin/notes"}>Back to notes</.back>

View file

@ -0,0 +1,14 @@
<.simple_form :let={f} for={@changeset} action={@action}>
<.error :if={@changeset.action}>
Oops, something went wrong! Please check the errors below.
</.error>
<.input field={f[:name]} type="text" />
<.input field={f[:content]} type="textarea" label="Content" rows="5" class="font-mono" />
<.input field={f[:regex]} type="text" label="Regex" />
<.input field={f[:slug]} type="text" label="Slug" />
<:actions>
<.button>Save Tag</.button>
</:actions>
</.simple_form>

View file

@ -22,18 +22,9 @@ defmodule ChiyaWeb.Indie.Micropub do
end
end
def find_note(note_url) do
slug = Chiya.Notes.Note.note_slug(note_url)
note = Chiya.Notes.get_note_by_slug_preloaded(slug)
if is_nil(note) do
{:error, :invalid_request}
else
{:ok, note}
end
end
def update_note(note, replace, add, delete) do
Logger.info("Updating note..")
with {:ok, note_attrs} <- get_update_attrs(replace, add, delete),
{:ok, note} <- Chiya.Notes.update_note(note, note_attrs) do
Logger.info("Note updated!")
@ -47,6 +38,28 @@ defmodule ChiyaWeb.Indie.Micropub do
end
end
def find_note(note_url) do
Logger.info("Looking up note by url #{note_url}")
case Chiya.Notes.Note.note_slug(note_url) do
{:ok, slug} ->
Logger.info("Found note with slug #{slug}, fetching note.")
note = Chiya.Notes.get_note_by_slug_preloaded(slug)
if is_nil(note) do
Logger.error("Note with #{note_url} was not found.")
{:error, :invalid_request}
else
Logger.info("Note found!")
{:ok, note}
end
_ ->
Logger.error("Note with #{note_url} was not found.")
{:error, :invalid_request}
end
end
defp create_photos(note, properties) do
properties
|> Props.get_photos()
@ -98,6 +111,8 @@ defmodule ChiyaWeb.Indie.Micropub do
|> Enum.into(replace_attrs)
|> Enum.into(add_attrs)
Logger.info("Update attributes: #{inspect(attrs)}")
{:ok, attrs}
end
@ -124,11 +139,11 @@ defmodule ChiyaWeb.Indie.Micropub do
tags = Props.get_tags(source)
attrs =
if !Enum.empty?(tags) do
if Enum.empty?(tags) do
attrs
else
tags_string = Enum.join(tags, ",")
Map.put(attrs, :tags_string, tags_string)
else
attrs
end
attrs
@ -141,7 +156,9 @@ defmodule ChiyaWeb.Indie.Micropub do
defp verify_app_token(access_token) do
token = Chiya.Accounts.get_app_token("obsidian", "app")
if not is_nil(token) do
if is_nil(token) do
{:error, :insufficient_scope, "Could not verify app token"}
else
token_string =
token.token
|> :crypto.bytes_to_integer()
@ -152,8 +169,6 @@ defmodule ChiyaWeb.Indie.Micropub do
else
{:error, :insufficient_scope, "Could not verify app token"}
end
else
{:error, :insufficient_scope, "Could not verify app token"}
end
end
@ -163,6 +178,8 @@ defmodule ChiyaWeb.Indie.Micropub do
|> get_base_attrs()
|> get_channel(channel_id)
Logger.info("Note attributes: #{inspect(attrs)}")
{:ok, attrs}
end
@ -176,6 +193,8 @@ defmodule ChiyaWeb.Indie.Micropub do
|> Map.put_new(:url, url)
|> Map.put_new(:kind, :bookmark)
Logger.info("Bookmark attributes: #{inspect(attrs)}")
{:ok, attrs}
end
@ -196,6 +215,8 @@ defmodule ChiyaWeb.Indie.Micropub do
published_at: published_at
}
Logger.info("Base attributes: #{inspect(attrs)}")
attrs
end

View file

@ -1,5 +1,5 @@
defmodule ChiyaWeb.Indie.MicropubHandler do
@behaviour PlugMicropub.HandlerBehaviour
@behaviour ChiyaWeb.Plugs.PlugMicropub.HandlerBehaviour
require Logger
use Phoenix.VerifiedRoutes,
@ -25,6 +25,14 @@ defmodule ChiyaWeb.Indie.MicropubHandler do
%{
"type" => "bookmark",
"name" => "Bookmark"
},
%{
"type" => "like",
"name" => "Like"
},
%{
"type" => "repost",
"name" => "Repost"
}
]
@ -91,7 +99,7 @@ defmodule ChiyaWeb.Indie.MicropubHandler do
}
filtered_note =
Map.filter(note, fn {key, _val} ->
Map.filter(properties, fn {key, _val} ->
Enum.member?(filter_properties, to_string(key))
end)

View file

@ -1,17 +1,14 @@
defmodule ChiyaWeb.Indie.Properties do
def get_post_type(properties) do
cond do
Map.has_key?(properties, "like-of") ->
{:ok, :like}
Map.has_key?(properties, "bookmark-of") ->
{:ok, :bookmark}
Map.has_key?(properties, "content") ->
{:ok, :note}
true ->
{:error, :unsupported_posttype}
Map.has_key?(properties, "like-of") -> {:ok, :like}
Map.has_key?(properties, "bookmark-of") -> {:ok, :bookmark}
Map.has_key?(properties, "content") -> {:ok, :note}
Map.has_key?(properties, "repost-of") -> {:ok, :repost}
Map.has_key?(properties, "read-of") -> {:ok, :read}
Map.has_key?(properties, "watch-of") -> {:ok, :watch}
Map.has_key?(properties, "listen-of") -> {:ok, :listen}
true -> {:error, :unsupported_posttype}
end
end

View file

@ -37,8 +37,6 @@ defmodule ChiyaWeb.AdminHomeLive do
|> push_navigate(to: ~p"/note/#{note.slug}")}
{:error, %Ecto.Changeset{} = changeset} ->
IO.inspect(changeset)
{:noreply,
socket
|> put_flash(:error, "Could not create note!")

View file

@ -0,0 +1,132 @@
defmodule ChiyaWeb.NoteEditLive do
use ChiyaWeb, :live_view
def render(assigns) do
~H"""
<.header>
<:actions>
<.link href={~p"/admin/notes/#{@note.id}"}>
<.button><.icon name="hero-arrow-left" /> Back to Note</.button>
</.link>
</:actions>
</.header>
<.simple_form for={@note_form} id="note_form" phx-change="validate_note" phx-submit="update_note">
<header>
<.input
field={@note_form[:name]}
type="text"
class="border-none dark:border-none text-2xl dark:text-2xl"
/>
<.input field={@note_form[:slug]} type="text" class="bg-gray-200 dark:bg-gray-900 font-mono" />
</header>
<section class="grid grid-cols-5 gap-6">
<section class="col-span-5 md:col-span-3">
<.input
field={@note_form[:content]}
type="textarea"
label="Content"
rows="20"
class="font-mono"
/>
</section>
<section class="col-span-5 md:col-span-2 flex flex-col gap-6">
<.input field={@note_form[:published_at]} type="datetime-local" label="Published at" />
<.input
field={@note_form[:kind]}
type="select"
label="Kind"
prompt="Choose a value"
options={Ecto.Enum.values(Chiya.Notes.Note, :kind)}
/>
<.input field={@note_form[:url]} type="text" label="Url" />
<.input
field={@note_form[:tags_string]}
type="text"
label="Tags"
value={tags_to_string(@note.tags)}
/>
<.input
field={@note_form[:channels]}
type="select"
label="Channel"
multiple={true}
options={@channels}
value={@selected_channels}
/>
</section>
</section>
<:actions>
<.button>Save Note</.button>
</:actions>
</.simple_form>
"""
end
def mount(%{"id" => id}, _session, socket) do
note = Chiya.Notes.get_note_preloaded!(id)
changeset = Chiya.Notes.change_note(note)
selected_channels = Enum.map(note.channels, fn c -> c.id end)
{:ok,
socket
|> assign(%{
note_form: to_form(changeset),
note: note,
action: ~p"/admin/notes/#{note}",
channels: to_channel_options(),
selected_channels: selected_channels
})}
end
def handle_event("validate_note", params, socket) do
%{"note" => note_params} = params
note_form =
socket.assigns.note
|> Chiya.Notes.change_note(note_params)
|> Map.put(:action, :validate)
|> to_form()
{:noreply, socket |> assign(:note_form, note_form)}
end
def handle_event("update_note", params, socket) do
%{"note" => note_params} = params
note_params = from_channel_ids(note_params)
note = socket.assigns.note
case Chiya.Notes.update_note(note, note_params) do
{:ok, note} ->
{:noreply,
socket
|> put_flash(:info, "Note updated successfully.")
|> redirect(to: ~p"/admin/notes/#{note}")}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, :note_form, to_form(Map.put(changeset, :action, :update)))}
end
end
def tags_to_string(tags), do: Enum.map_join(tags, ", ", fn t -> t.name end)
defp from_channel_ids(note_params) do
selected_ids = Enum.map(note_params["channels"] || [], &String.to_integer/1)
selected_channels =
Chiya.Channels.list_channels()
|> Enum.filter(fn c -> Enum.member?(selected_ids, c.id) end)
Map.put(note_params, "channels", selected_channels)
end
defp to_channel_options(items \\ nil),
do:
Enum.map(items || Chiya.Channels.list_channels(), fn c ->
{Chiya.Channels.Channel.icon(c) <> " " <> c.name, c.id}
end)
end

View file

@ -0,0 +1,95 @@
defmodule ChiyaWeb.NoteListLive do
use ChiyaWeb, :live_view
@impl true
def mount(_params, __session, socket) do
channels = Chiya.Channels.list_channels() |> Chiya.Channels.preload_channel()
{:ok, {notes, meta}} = Chiya.Notes.list_admin_notes(%{})
{:ok,
socket
|> assign(%{
channels: channels,
notes: notes,
meta: meta
})}
end
@impl true
def handle_params(params, _uri, socket) do
case Chiya.Notes.list_admin_notes(params) do
{:ok, {notes, meta}} ->
{:noreply, socket |> assign(%{notes: notes, meta: meta})}
{:error, data} ->
{:noreply, socket}
end
end
@impl true
def handle_event("update-filter", params, socket) do
params = Map.delete(params, "_target")
{:noreply, push_patch(socket, to: ~p"/admin/notes?#{params}")}
end
defp channel_list(assigns) do
channels =
assigns.channels
|> Enum.map(fn c ->
{"#{c.name} (#{Enum.count(c.notes)})", c.name}
end)
[{"All", nil}] ++ channels
end
@impl true
def render(assigns) do
~H"""
<.header>
<.icon name="hero-document-text" /> Notes
<:subtitle>Notes are the content, the heart of your site.</:subtitle>
<:actions>
<.link href={~p"/admin/notes/new"}>
<.button><.icon name="hero-plus-small" /> New Note</.button>
</.link>
<.link href={~p"/admin/notes/import"}>
<.button><.icon name="hero-arrow-down-tray" /> Import Note</.button>
</.link>
</:actions>
</.header>
<section>
<.filter_form
fields={[
name: [op: :ilike_and],
channels: [
op: :ilike_and,
type: "select",
options: channel_list(assigns)
]
]}
meta={@meta}
id="user-filter-form"
/>
</section>
<section class="flex flex-col gap-3 mt-6">
<%= for note <- @notes do %>
<article class="bg-slate-100 dark:bg-slate-800 text-slate-900 dark:text-slate-100 p-3 rounded">
<header>
<h2 class="text-xl leading-normal">
<a href={"/admin/notes/#{note.id}"}><%= note.name %></a>
</h2>
</header>
<footer class="flex gap-3 text-sm ">
<span>Updated <%= from_now(note.updated_at) %></span>
<span>Published <%= from_now(note.published_at) %></span>
</footer>
</article>
<% end %>
</section>
<Flop.Phoenix.pagination meta={@meta} path={~p"/admin/notes"} />
"""
end
end

View file

@ -37,20 +37,31 @@ defmodule ChiyaWeb.NoteShowLive do
</:actions>
</.header>
<.list>
<:item title="Published at">
<%= pretty_date(@note.published_at) %> <span>(<%= from_now(@note.published_at) %>)</span>
</:item>
<:item title="Channels"><%= note_channels(@note.channels) %></:item>
<:item title="Kind"><%= @note.kind %></:item>
<:item title="Url"><%= @note.url %></:item>
<:item title="Tags"><%= note_tags(@note.tags) %></:item>
<:item title="Links outgoing"><%= note_links(@note.links_from) %></:item>
<:item title="Links incoming"><%= note_links(@note.links_to) %></:item>
<:item title="Embed">
<pre class="p-1 bg-gray-100 text-black rounded select-all">[[<%= @note.slug %>]]</pre>
</:item>
</.list>
<section class="mt-4">
<div class="select-all font-mono bg-white p-1 rounded">[[<%= @note.slug %>]]</div>
</section>
<section class="grid grid-cols-2">
<section class="col-span-1">
<.list>
<:item title="Published at">
<%= pretty_date(@note.published_at) %> <span>(<%= from_now(@note.published_at) %>)</span>
</:item>
<:item title="Channels"><%= note_channels(@note.channels) %></:item>
<:item title="Kind"><%= @note.kind %></:item>
<:item title="Url"><a href={@note.url} target="_blank"><%= @note.url %></a></:item>
<:item title="Tags"><%= note_tags(@note.tags) %></:item>
<:item title="Links outgoing"><%= note_links(@note.links_from) %></:item>
<:item title="Links incoming"><%= note_links(@note.links_to) %></:item>
</.list>
</section>
<section class="col-span-1 p-6">
<section class="prose">
<%= raw(Markdown.render(@note.content)) %>
</section>
</section>
</section>
<.line />

View file

@ -0,0 +1,38 @@
defmodule ChiyaWeb.OutlineRenderer do
import Phoenix.HTML, only: [safe_to_string: 1]
import Phoenix.HTML.Tag, only: [content_tag: 3, content_tag: 2]
def render_outline(content) do
children =
content
|> ChiyaWeb.Outline.get()
|> Enum.map(&do_render_outline/1)
root = content_tag(:ul, do: children)
safe_to_string(root)
end
def has_outline?(content) do
outline_empty =
content
|> ChiyaWeb.Outline.get()
|> Enum.empty?()
!outline_empty
end
def do_render_outline(%{text: text, children: children, level: _level}) do
slug = Slugger.slugify_downcase(text)
list_item = content_tag(:li, do: content_tag(:a, text, href: "##{slug}"))
if Enum.empty?(children) do
[list_item]
else
[
list_item,
content_tag(:ul, do: Enum.map(children, &do_render_outline/1))
]
end
end
end

View file

@ -0,0 +1,416 @@
defmodule ChiyaWeb.Plugs.PlugMicropub do
@moduledoc """
A Plug for building a Micropub server.
To use:
"""
use Plug.Router
require Logger
plug :match
plug :dispatch
# Plug Callbacks
@doc false
def init(opts) do
logging =
Keyword.get(opts, :logging) || false
handler =
Keyword.get(opts, :handler) || raise ArgumentError, "Micropub Plug requires :handler option"
json_encoder =
Keyword.get(opts, :json_encoder) ||
raise ArgumentError, "Micropub Plug requires :json_encoder option"
[handler: handler, json_encoder: json_encoder, logging: logging]
end
@doc false
def call(conn, opts) do
conn = put_private(conn, :plug_micropub, opts)
super(conn, opts)
end
# Routes
post "/" do
with {:ok, access_token, conn} <- get_access_token(conn),
{:ok, action, conn} <- get_action(conn) do
Logger.info("Micropub: Handling action [#{action}]")
Logger.info("Micropub: Request Body #{inspect(conn.body_params)}")
handle_action(action, access_token, conn)
else
error -> send_error(conn, error)
end
end
get "/" do
with {:ok, access_token, conn} <- get_access_token(conn),
{:ok, query} <- get_query(conn) do
Logger.info("Micropub: Handling query [#{query}]")
handle_query(query, access_token, conn)
else
error -> send_error(conn, error)
end
end
post "/media" do
handler = conn.private[:plug_micropub][:handler]
Logger.info("Micropub: Handling media")
with {:ok, access_token, conn} <- get_access_token(conn),
{:ok, file} <- get_file(conn),
{:ok, url} <- handler.handle_media(file, access_token) do
conn
|> put_resp_header("location", url)
|> send_resp(:created, "")
else
error -> send_error(conn, error)
end
end
match _ do
Logger.warning("Micropub: Unsupported url")
send_error(conn, {:error, :invalid_request})
end
# Internal Functions
defp send_content(conn, content) do
json_encoder = conn.private[:plug_micropub][:json_encoder]
body = json_encoder.encode!(content)
conn
|> put_resp_content_type("application/json")
|> send_resp(:ok, body)
end
defp send_error(conn, {:error, error}) do
body = %{error: error}
_send_error(conn, body)
end
defp send_error(conn, {:error, error, description}) do
body = %{error: error, error_description: description}
_send_error(conn, body)
end
defp _send_error(conn, body) do
json_encoder = conn.private[:plug_micropub][:json_encoder]
code = get_error_code(body.error)
body = json_encoder.encode!(body)
Logger.warning("Micropub: Sending error with code #{code}: #{inspect(body)}")
conn
|> put_resp_content_type("application/json")
|> send_resp(code, body)
end
defp get_error_code(:insufficient_scope), do: :unauthorized
defp get_error_code(:invalid_request), do: :bad_request
defp get_error_code(code), do: code
defp get_action(conn) do
{action, body_params} = Map.pop(conn.body_params, "action")
conn = %Plug.Conn{conn | body_params: body_params}
case action do
nil ->
{:ok, :create, conn}
action when action in ["delete", "undelete", "update"] ->
{:ok, String.to_existing_atom(action), conn}
_ ->
{:error, :invalid_request}
end
end
defp get_query(conn) do
case Map.fetch(conn.query_params, "q") do
{:ok, query} when query in ["config", "source", "syndicate-to", "channel", "category"] ->
{:ok, String.to_existing_atom(query)}
_ ->
{:error, :invalid_request}
end
end
defp get_file(conn) do
case Map.fetch(conn.body_params, "file") do
{:ok, file} -> {:ok, file}
:error -> {:error, :invalid_request}
end
end
defp get_access_token(conn) do
{access_token, body_params} = Map.pop(conn.body_params, "access_token")
conn = %Plug.Conn{conn | body_params: body_params}
case access_token do
nil -> parse_auth_header(conn)
access_token -> {:ok, access_token, conn}
end
end
defp parse_auth_header(conn) do
with [header] <- get_req_header(conn, "authorization"),
"Bearer" <> token <- header,
do: {:ok, String.trim(token), conn},
else: (_ -> {:error, :unauthorized})
end
defp handle_action(:create, access_token, conn) do
Logger.info("Micropub: Handle create")
content_type = conn |> get_req_header("content-type") |> List.first()
Logger.info("Micropub: Content type #{content_type}")
handler = conn.private[:plug_micropub][:handler]
with {:ok, type, properties} <- parse_create_body(content_type, conn.body_params),
{:ok, code, url} <- handler.handle_create(type, properties, access_token) do
conn
|> put_resp_header("location", url)
|> send_resp(code, "")
else
error ->
Logger.warning("Micropub: Error while handling create: #{inspect(error)}")
send_error(conn, error)
end
end
defp handle_action(:update, access_token, conn) do
Logger.info("Micropub: Handle update")
content_type = conn |> get_req_header("content-type") |> List.first()
Logger.info("Micropub: Content type #{content_type}")
with "application/json" <- content_type,
{url, properties} when is_binary(url) <- Map.pop(conn.body_params, "url"),
{:ok, replace, add, delete} <- parse_update_properties(properties) do
do_update(conn, access_token, url, replace, add, delete)
else
error ->
Logger.warning("Micropub: Error while handling update: #{inspect(error)}")
send_error(conn, {:error, :invalid_request})
end
end
defp handle_action(:delete, access_token, conn) do
Logger.info("Micropub: Handle delete")
case Map.fetch(conn.body_params, "url") do
{:ok, url} ->
do_delete(conn, access_token, url)
_ ->
send_error(conn, {:error, :invalid_request})
end
end
defp handle_action(:undelete, access_token, conn) do
Logger.info("Micropub: Handle undelete")
case Map.fetch(conn.body_params, "url") do
{:ok, url} ->
do_undelete(conn, access_token, url)
_ ->
send_error(conn, {:error, :invalid_request})
end
end
defp handle_query(:config, access_token, conn) do
Logger.info("Micropub: Handle config query")
handler = conn.private[:plug_micropub][:handler]
case handler.handle_config_query(access_token) do
{:ok, content} ->
send_content(conn, content)
error ->
Logger.warning("Micropub: Error while handling config: #{inspect(error)}")
send_error(conn, error)
end
end
defp handle_query(:category, access_token, conn) do
Logger.info("Micropub: Handle category query")
handler = conn.private[:plug_micropub][:handler]
case handler.handle_category_query(access_token) do
{:ok, content} ->
send_content(conn, content)
error ->
Logger.warning("Micropub: Error while handling category: #{inspect(error)}")
send_error(conn, error)
end
end
defp handle_query(:source, access_token, conn) do
Logger.info("Micropub: Handle source query")
case Map.fetch(conn.query_params, "url") do
{:ok, url} ->
do_source_query(conn, access_token, url)
error ->
Logger.warning("Micropub: Error while handling source: #{inspect(error)}")
send_error(conn, {:error, :invalid_request})
end
end
defp handle_query(:"syndicate-to", access_token, conn) do
Logger.info("Micropub: Handle syndicate-to query")
handler = conn.private[:plug_micropub][:handler]
case handler.handle_syndicate_to_query(access_token) do
{:ok, content} ->
send_content(conn, content)
error ->
Logger.warning("Micropub: Error while handling syndicate-to: #{inspect(error)}")
send_error(conn, error)
end
end
defp handle_query(:channel, access_token, conn) do
Logger.info("Micropub: Handle channel query")
handler = conn.private[:plug_micropub][:handler]
case handler.handle_channel_query(access_token) do
{:ok, content} ->
send_content(conn, content)
error ->
Logger.warning("Micropub: Error while handling channel: #{inspect(error)}")
send_error(conn, error)
end
end
defp parse_update_properties(properties) do
properties = Map.take(properties, ["replace", "add", "delete"])
valid? =
Enum.all?(properties, fn
{"delete", prop} when is_list(prop) ->
Enum.all?(prop, &is_binary/1)
{_k, prop} when is_map(prop) ->
Logger.debug("Micropub: Parsing Add/Replace maps")
Enum.all?(prop, fn
{_k, v} when is_list(v) ->
true
_ ->
Logger.warning("Micropub: Property value of #{prop} is not a list")
false
end)
_ ->
false
end)
Logger.info("Valid check successful: #{valid?}")
if valid? do
replace = Map.get(properties, "replace", %{})
add = Map.get(properties, "add", %{})
delete = Map.get(properties, "delete", %{})
{:ok, replace, add, delete}
else
:error
end
end
defp do_update(conn, access_token, url, replace, add, delete) do
handler = conn.private[:plug_micropub][:handler]
case handler.handle_update(url, replace, add, delete, access_token) do
:ok ->
send_resp(conn, :no_content, "")
{:ok, url} ->
conn
|> put_resp_header("location", url)
|> send_resp(:created, "")
error ->
send_error(conn, error)
end
end
defp do_delete(conn, access_token, url) do
handler = conn.private[:plug_micropub][:handler]
case handler.handle_delete(url, access_token) do
:ok -> send_resp(conn, :no_content, "")
error -> send_error(conn, error)
end
end
defp do_undelete(conn, access_token, url) do
handler = conn.private[:plug_micropub][:handler]
case handler.handle_undelete(url, access_token) do
:ok ->
send_resp(conn, :no_content, "")
{:ok, url} ->
conn
|> put_resp_header("location", url)
|> send_resp(:created, "")
error ->
send_error(conn, error)
end
end
defp do_source_query(conn, access_token, url) do
handler = conn.private[:plug_micropub][:handler]
properties = Map.get(conn.query_params, "properties", [])
case handler.handle_source_query(url, properties, access_token) do
{:ok, content} -> send_content(conn, content)
error -> send_error(conn, error)
end
end
defp parse_create_body("application/json", params) do
with {:ok, ["h-" <> type]} <- Map.fetch(params, "type"),
{:ok, properties} when is_map(properties) <- Map.fetch(params, "properties") do
properties = Map.new(properties)
Logger.info("Micropub: Parsed properties #{inspect(properties)}")
{:ok, type, properties}
else
_ -> {:error, :invalid_request}
end
end
defp parse_create_body(_, params) do
with {type, params} when is_binary(type) <- Map.pop(params, "h") do
properties =
params
|> Enum.map(fn {k, v} -> {k, List.wrap(v)} end)
|> Map.new()
Logger.info("Micropub: Parsed properties #{inspect(properties)}")
{:ok, type, properties}
else
_ -> {:error, :invalid_request}
end
end
end

View file

@ -0,0 +1,61 @@
defmodule ChiyaWeb.Plugs.PlugMicropub.HandlerBehaviour do
@moduledoc """
Behaviour defining the interface for a PlugMicropub Handler
"""
@type access_token :: String.t()
@type handler_error_atom :: :invalid_request | :forbidden | :insufficient_scope
@type handler_error ::
{:error, handler_error_atom} | {:error, handler_error_atom, description :: String.t()}
@callback handle_create(type :: String.t(), properties :: map, access_token) ::
{:ok, :created | :accepted, url :: String.t()}
| handler_error
@callback handle_update(
url :: String.t(),
replace :: map,
add :: map,
delete :: map,
access_token
) ::
:ok
| {:ok, url :: String.t()}
| handler_error
@callback handle_delete(url :: String.t(), access_token) ::
:ok
| handler_error
@callback handle_undelete(url :: String.t(), access_token) ::
:ok
| {:ok, url :: String.t()}
| handler_error
@callback handle_config_query(access_token) ::
{:ok, map}
| handler_error
@callback handle_channel_query(access_token) ::
{:ok, map}
| handler_error
@callback handle_category_query(access_token) ::
{:ok, map}
| handler_error
@callback handle_syndicate_to_query(access_token) ::
{:ok, map}
| handler_error
@callback handle_source_query(
url :: String.t(),
properties :: [String.t()],
access_token
) ::
{:ok, map}
| handler_error
@callback handle_media(file :: Plug.Upload.t(), access_token) ::
{:ok, url :: String.t()} | handler_error
end

View file

@ -76,7 +76,7 @@ defmodule ChiyaWeb.References do
end
defp get_link_class(valid) do
if not valid, do: "{:.invalid}", else: ""
if valid, do: "", else: "{:.invalid}"
end
defp map_to_tuple([placeholder, note_slug]),

View file

@ -40,7 +40,7 @@ defmodule ChiyaWeb.Router do
## Indie routes
scope "/indie" do
forward "/micropub",
PlugMicropub,
ChiyaWeb.Plugs.PlugMicropub,
handler: ChiyaWeb.Indie.MicropubHandler,
json_encoder: Jason
end
@ -70,16 +70,23 @@ defmodule ChiyaWeb.Router do
live "/", AdminHomeLive, :index
resources "/channels", ChannelController
resources "/notes", NoteController, except: [:show]
resources "/notes", NoteController, except: [:show, :index, :edit]
resources "/settings", SettingController, singleton: true
resources "/identities", IdentityController
resources "/comments", CommentController, only: [:index, :show]
resources "/tokens", TokenController, only: [:index, :show, :new, :create, :delete]
resources "/tags", TagController, only: [:index, :edit, :update, :show]
get "/tags/:id/apply", TagController, :apply_prepare
get "/tags/:id/apply/run", TagController, :apply_run
get "/notes/import", NoteController, :import_prepare
post "/notes/import", NoteController, :import_run
live "/notes", NoteListLive, :index
live "/notes/:id", NoteShowLive, :show
live "/notes/:id/edit", NoteEditLive, :edit
get "/notes/:id/raw", NoteController, :raw
get "/notes/:id/publish", NoteController, :publish
get "/notes/:id/unpublish", NoteController, :unpublish

View file

@ -35,23 +35,27 @@ defmodule Chiya.MixProject do
{:bcrypt_elixir, "~> 3.0"},
{:cors_plug, "~> 3.0"},
{:coverex, "~> 1.5", only: :test},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:earmark, "~> 1.4"},
{:ecto_autoslug_field, "~> 3.0"},
{:ecto_sql, "~> 3.6"},
{:finch, "~> 0.16"},
{:floki, ">= 0.30.0", only: :test},
{:flop, "~> 0.22.1"},
{:flop_phoenix, "~> 0.21.1"},
{:gettext, "~> 0.23"},
{:jason, "~> 1.2"},
{:oban, "~> 2.14"},
{:phoenix, "~> 1.7.1"},
{:phoenix_active_link, "~> 0.3.2"},
{:phoenix_ecto, "~> 4.4"},
{:phoenix_html, "~> 3.3"},
{:phoenix_live_dashboard, "~> 0.8.0"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_view, "~> 0.19"},
{:plug_cowboy, "~> 2.5"},
{:plug_micropub, git: "https://git.inhji.de/inhji/plug_micropub.git", branch: "chiya"},
{:postgrex, ">= 0.0.0"},
{:slugger, "~> 0.3"},
{:swoosh, "~> 1.11"},
{:tailwind, "~> 0.2.0", runtime: Mix.env() == :dev},
{:telemetry_metrics, "~> 0.6"},

View file

@ -1,27 +1,31 @@
%{
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.1.0", "0b110a9a6c619b19a7f73fa3004aa11d6e719a67e672d1633dc36b6b2290a0f7", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ad2acb5a8bc049e8d5aa267802631912bb80d5f4110a178ae7999e69dca1bf7"},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"},
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
"cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"},
"coverex": {:hex, :coverex, "1.5.0", "a4248302f09562993041f1b056866bfd5688d3a03c429de80c47ea6663989ecc", [:mix], [{:hackney, "~> 1.5", [hex: :hackney, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "21a8f6e734a277b86c01b3cc44b7cc8b77cb1886adbebb708eefc6398567dcac"},
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
"credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"},
"db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"earmark": {:hex, :earmark, "1.4.39", "acdb2f02c536471029dbcc509fbd6b94b89f40ad7729fb3f68f4b6944843f01d", [:mix], [{:earmark_parser, "~> 1.4.33", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "156c9d8ec3cbeccdbf26216d8247bdeeacc8c76b4d9eee7554be2f1b623ea440"},
"earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"},
"earmark": {:hex, :earmark, "1.4.40", "ff1a0f8bf3b298113c2a257c4e7a8b29ba9db5d35f5ee6d29291cb8caa09a071", [:mix], [{:earmark_parser, "~> 1.4.35", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "5fb622d5e36046bc313a426211e8bf769ba50db7720744859a21932c6470d75c"},
"earmark_parser": {:hex, :earmark_parser, "1.4.35", "437773ca9384edf69830e26e9e7b2e0d22d2596c4a6b17094a3b29f01ea65bb8", [:mix], [], "hexpm", "8652ba3cb85608d0d7aa2d21b45c6fad4ddc9a1f9a1f1b30ca3a246f0acc33f6"},
"ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"},
"ecto_autoslug_field": {:hex, :ecto_autoslug_field, "3.0.0", "37fbc2f07e6691136afff246f2cf5b159ad395b665a55d06db918975fd2397db", [:mix], [{:ecto, ">= 3.7.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:slugger, ">= 0.3.0", [hex: :slugger, repo: "hexpm", optional: false]}], "hexpm", "8ec252c7cf85f13132062f56a484d6a0ef1f981f7be9ce4ad7e9546dd8c0cc0f"},
"ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"},
"ecto_autoslug_field": {:hex, :ecto_autoslug_field, "3.1.0", "ddf26e814baf3c32c6aebfed56a637f10a097db83f65d71e6f2d1e7faf2e9e51", [:mix], [{:ecto, ">= 3.7.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:slugify, "~> 1.3", [hex: :slugify, repo: "hexpm", optional: false]}], "hexpm", "b6ddd614805263e24b5c169532c934440d0289181cce873061fca3a8e92fd9ff"},
"ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"},
"elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"},
"expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"},
"floki": {:hex, :floki, "0.34.3", "5e2dcaec5d7c228ce5b1d3501502e308b2d79eb655e4191751a1fe491c37feac", [:mix], [], "hexpm", "9577440eea5b97924b4bf3c7ea55f7b8b6dce589f9b28b096cc294a8dc342341"},
"gettext": {:hex, :gettext, "0.23.0", "8065a6eb5a6c94ef3909b23178ffc064cd43a51d977b6c80babc8bb0c5ebfd6f", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "09ca27284fe5a82449eb252f2877e551a46b1c9c7d10d72d087211f0bfed9638"},
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~> 2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
"flop": {:hex, :flop, "0.22.1", "4eb5dc1c159845e31d33cd8e73e249b84d013d3b9b1fb17619e70d55fbd2b025", [:mix], [{:ecto, "~> 3.10.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}], "hexpm", "2f212238d92a8fcc2adadf20dcd875c6f8b4e1ce38ca6bc3f805918a5be567e4"},
"flop_phoenix": {:hex, :flop_phoenix, "0.21.1", "7b7c143e751f985a94624461a25916d1c604a8a3913253f9a38ae619577853d7", [:mix], [{:flop, "~> 0.22.0", [hex: :flop, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6.0 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "c44ccdda6d8b8dba4b7c885b7173150dc7388e1f5d63bd834d136fbd538f7b4b"},
"gettext": {:hex, :gettext, "0.23.1", "821e619a240e6000db2fc16a574ef68b3bd7fe0167ccc264a81563cc93e67a31", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "19d744a36b809d810d610b57c27b934425859d158ebd56561bc41f7eeb8795db"},
"hackney": {:hex, :hackney, "1.18.2", "d7ff544ddae5e1cb49e9cf7fa4e356d7f41b283989a1c304bfc47a8cc1cf966f", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "af94d5c9f97857db257090a4a10e5426ecb6f4918aa5cc666798566ae14b65fd"},
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
@ -31,12 +35,13 @@
"mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"},
"nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"},
"nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"},
"oban": {:hex, :oban, "2.15.2", "8f934a49db39163633965139c8846d8e24c2beb4180f34a005c2c7c3f69a6aa2", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0f4a579ea48fc7489e0d84facf8b01566e142bdc6542d7dabce32c10e664f1e9"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"oban": {:hex, :oban, "2.15.4", "d49ab4ffb7153010e32f80fe9e56f592706238149ec579eb50f8a4e41d218856", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5fce611fdfffb13e9148df883116e5201adf1e731eb302cc88cde0588510079c"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
"phoenix": {:hex, :phoenix, "1.7.7", "4cc501d4d823015007ba3cdd9c41ecaaf2ffb619d6fb283199fa8ddba89191e0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8966e15c395e5e37591b6ed0bd2ae7f48e961f0f60ac4c733f9566b519453085"},
"phoenix_active_link": {:hex, :phoenix_active_link, "0.3.2", "946d013b0839341d85ac4259fadf944d9767186e424a78ef37d310beac92d840", [:mix], [{:phoenix_html, "~> 2.10 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "81fcd4333541d6f1586e1d5fd0c34efed9bc4129573862bea340e234b6f22aeb"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.2", "b21bd01fdeffcfe2fab49e4942aa938b6d3e89e93a480d4aee58085560a0bc0d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "70242edd4601d50b69273b057ecf7b684644c19ee750989fd555625ae4ce8f5d"},
"phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.0", "0b3158b5b198aa444473c91d23d79f52fb077e807ffad80dacf88ce078fa8df2", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "87785a54474fed91a67a1227a741097eb1a42c2e49d3c0d098b588af65cd410d"},
"phoenix_html": {:hex, :phoenix_html, "3.3.2", "d6ce982c6d8247d2fc0defe625255c721fb8d5f1942c5ac051f6177bffa5973f", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "44adaf8e667c1c20fb9d284b6b0fa8dc7946ce29e81ce621860aa7e96de9a11d"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.1", "c4f2a2d3b26e6ca684d162ccf18aaeed8bed2181896e0393d0a2959789482e51", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1ca0f954274ce1916f771f86b3d49a91d3447e7c32d171660676095c5f30abe9"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.19.5", "6e730595e8e9b8c5da230a814e557768828fd8dfeeb90377d2d8dbb52d4ec00a", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b2eaa0dd3cfb9bd7fb949b88217df9f25aed915e986a28ad5c8a0d054e7ca9d3"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
@ -46,11 +51,14 @@
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
"plug_micropub": {:git, "https://git.inhji.de/inhji/plug_micropub.git", "1845a4067703fc656fec34b8732b9544967893c0", [branch: "chiya"]},
"poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"},
"postgrex": {:hex, :postgrex, "0.17.2", "a3ec9e3239d9b33f1e5841565c4eb200055c52cc0757a22b63ca2d529bbe764c", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "80a918a9e9531d39f7bd70621422f3ebc93c01618c645f2d91306f50041ed90c"},
"postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"scrivener": {:hex, :scrivener, "2.7.2", "1d913c965ec352650a7f864ad7fd8d80462f76a32f33d57d1e48bc5e9d40aba2", [:mix], [], "hexpm", "7866a0ec4d40274efbee1db8bead13a995ea4926ecd8203345af8f90d2b620d9"},
"scrivener_html": {:git, "https://github.com/workshops-de/scrivener_html", "80752fe8fcb7cd616adf6b5e78c226f158013ddf", []},
"slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm", "20d0ded0e712605d1eae6c5b4889581c3460d92623a930ddda91e0e609b5afba"},
"slugify": {:hex, :slugify, "1.3.1", "0d3b8b7e5c1eeaa960e44dce94382bee34a39b3ea239293e457a9c5b47cc6fd3", [:mix], [], "hexpm", "cb090bbeb056b312da3125e681d98933a360a70d327820e4b7f91645c4d8be76"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"swoosh": {:hex, :swoosh, "1.11.4", "9b353f998cba3c5e101a0669559c2fb2757b5d9eb7db058bf08687d82e93e416", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d3390914022a456ae1604bfcb3431bd12509b2afe8c70296bae6c9dca4903d0f"},
"swoosh": {:hex, :swoosh, "1.11.6", "7093531c12a537839c7b0777276aaad9f38a887e61c1d517f3b6a8dc6e9040d9", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7fbb7bd0b674e344dbd3a15c33ca787357663ef534dde2cc5ee74d00f7427fd5"},
"tailwind": {:hex, :tailwind, "0.2.1", "83d8eadbe71a8e8f67861fe7f8d51658ecfb258387123afe4d9dc194eddc36b0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "e8a13f6107c95f73e58ed1b4221744e1eb5a093cd1da244432067e19c8c9a277"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
@ -60,8 +68,8 @@
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"waffle": {:hex, :waffle, "1.1.7", "518f9bdda7b9b3d0958ad6ab16066631ce028f5f12217382822a428895fc4be3", [:mix], [{:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:ex_aws_s3, "~> 2.1", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "e97e7b10b7f380687b5dc5e65b391538a802eff636605ad183e0bed29b45b0ef"},
"waffle_ecto": {:hex, :waffle_ecto, "0.0.12", "e5c17c49b071b903df71861c642093281123142dc4e9908c930db3e06795b040", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:waffle, "~> 1.0", [hex: :waffle, repo: "hexpm", optional: false]}], "hexpm", "585fe6371057066d2e8e3383ddd7a2437ff0668caf3f4cbf5a041e0de9837168"},
"websock": {:hex, :websock, "0.5.2", "b3c08511d8d79ed2c2f589ff430bd1fe799bb389686dafce86d28801783d8351", [:mix], [], "hexpm", "925f5de22fca6813dfa980fb62fd542ec43a2d1a1f83d2caec907483fe66ff05"},
"websock_adapter": {:hex, :websock_adapter, "0.5.3", "4908718e42e4a548fc20e00e70848620a92f11f7a6add8cf0886c4232267498d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "cbe5b814c1f86b6ea002b52dd99f345aeecf1a1a6964e209d208fb404d930d3d"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.4", "7af8408e7ed9d56578539594d1ee7d8461e2dd5c3f57b0f2a5352d610ddde757", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d2c238c79c52cbe223fcdae22ca0bb5007a735b9e933870e241fce66afb4f4ab"},
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
"yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"},
"yaml_front_matter": {:hex, :yaml_front_matter, "1.0.0", "9f3310906a9647d04fc4715b6bac5ed177defc9625c53b93430d36d8fd0961db", [:mix], [{:yaml_elixir, "~> 2.0", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "73cf825c18b7a14bfd8fb5856fa4887196035be69d2d45d1c370917aae0572ec"},

View file

@ -17,4 +17,3 @@ MIX_ENV=prod mix assets.deploy
echo "Generating release.."
MIX_ENV=prod mix release --overwrite

View file

@ -33,11 +33,20 @@ defmodule ChiyaWeb.MicropubTest do
assert note.content == "replaced content"
end
test "updates a note by replacing name" do
note = note_fixture()
assert {:ok, %Note{} = note} =
Micropub.update_note(note, %{"name" => ["replaced name"]}, %{}, %{})
assert note.name == "replaced name"
end
test "updates a note by adding categories" do
note = note_fixture()
assert {:ok, %Note{} = note} =
Micropub.update_note(note, %{}, %{"category" => ["foo", "bar"]}, %{})
Micropub.update_note(note, %{"category" => ["foo", "bar"]}, %{}, %{})
assert Enum.empty?(note.tags) == false
end