@raketa-cloud/admin 0.0.14 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/package.json +1 -1
  2. package/src/cloud/ArticleChanges.js +15 -0
  3. package/src/cloud/ArticleRevisions.js +152 -0
  4. package/src/cloud/Articles.js +133 -0
  5. package/src/cloud/documents/articlesDocuments.js +109 -469
  6. package/src/cloud/documents/common.js +10 -0
  7. package/src/handlers/article_revisions/ArticleRevisionsHandler.js +49 -0
  8. package/src/handlers/article_revisions/CodeHandler.js +90 -0
  9. package/src/handlers/article_revisions/GeneralHandler.js +95 -0
  10. package/src/handlers/article_revisions/LocksHandler.js +54 -0
  11. package/src/handlers/article_revisions/MetaHandler.js +90 -0
  12. package/src/handlers/article_revisions/PublishesHandler.js +90 -0
  13. package/src/handlers/article_revisions/RevertsHandler.js +28 -0
  14. package/src/handlers/article_revisions/TagHandler.js +89 -0
  15. package/src/handlers/article_revisions/UnlocksHandler.js +22 -0
  16. package/src/handlers/article_revisions/VisualHandler.js +84 -0
  17. package/src/handlers/article_revisions/WidgetHandler.js +47 -0
  18. package/src/handlers/articles/ArticlesHandler.js +119 -0
  19. package/src/handlers/articles/ClonesHandler.js +17 -0
  20. package/src/handlers/articles/DeletesHandler.js +68 -0
  21. package/src/handlers/articles/TranslationsHandler.js +103 -0
  22. package/src/handlers/articles/URLsHandler.js +71 -0
  23. package/src/handlers/pages/PagesHandler.js +4 -2
  24. package/src/handlers/pages/TranslationsHandler.js +7 -5
  25. package/src/index.js +22 -0
  26. package/src/routeArticles.js +65 -0
  27. package/src/routeStandard.js +2 -0
  28. package/src/setupCloud.js +12 -0
  29. package/src/tests/handlers/articles/ArticlesHandler.test/create.test.js +144 -0
  30. package/src/tests/handlers/articles/ArticlesHandler.test/create_form.test.js +41 -0
  31. package/src/tests/handlers/articles/ArticlesHandler.test/index.test.js +49 -0
  32. package/src/tests/handlers/articles/ArticlesHandler.test/show.test.js +105 -0
  33. package/src/tests/handlers/articles/ConesHandler.test/create.test.js +54 -0
  34. package/src/tests/handlers/articles/DeletesHandler.test/create.test.js +110 -0
  35. package/src/tests/handlers/articles/DeletesHandler.test/create_form.test.js +54 -0
  36. package/src/tests/handlers/articles/URLsHandler.test/create.test.js +155 -0
  37. package/src/tests/handlers/articles/URLsHandler.test/create_form.test.js +51 -0
  38. package/src/tests/handlers/pages/PagesHandler.test/create.test.js +3 -3
  39. package/src/utils.js +46 -0
  40. package/src/views/article_revisions/_nav.ejs +40 -0
  41. package/src/views/article_revisions/codes/edit_form.ejs +39 -0
  42. package/src/views/article_revisions/general/edit_form.ejs +70 -0
  43. package/src/views/article_revisions/locks/create_form.ejs +35 -0
  44. package/src/views/article_revisions/meta/edit_form.ejs +41 -0
  45. package/src/views/article_revisions/publishes/_form.ejs +57 -0
  46. package/src/views/article_revisions/publishes/create_form.ejs +10 -0
  47. package/src/views/article_revisions/tags/edit_form.ejs +64 -0
  48. package/src/views/article_revisions/visuals/edit_form.ejs +42 -0
  49. package/src/views/articles/_form.ejs +86 -0
  50. package/src/views/articles/create_form.ejs +9 -0
  51. package/src/views/articles/deletes/create_form.ejs +55 -0
  52. package/src/views/articles/edit_form.ejs +3 -0
  53. package/src/views/articles/index.ejs +161 -0
  54. package/src/views/articles/show.ejs +281 -0
  55. package/src/views/articles/translations/_form.ejs +77 -0
  56. package/src/views/articles/translations/create_form.ejs +11 -0
  57. package/src/views/articles/urls/create_form.ejs +31 -0
  58. package/src/views/page_revisions/locks/create_form.ejs +1 -1
  59. package/test/factory.js +37 -0
@@ -0,0 +1,42 @@
1
+ <div class="page-wrapper">
2
+ <div class="page-title">
3
+ <h1 class="heading-1">Edit revision / Visuals</h1>
4
+ </div>
5
+
6
+ <%- include('../_nav.ejs', { settings: visual_settings, active: 'visuals' }) %>
7
+
8
+ <div class="grid gap-4 md:grid-cols-3">
9
+ <div>
10
+ <% form_for({ visual_settings }, errors, (f) => { %>
11
+ <form action="<%= `/${_context.locale}/article_revisions/${visual_settings.id}/visuals` %>" method="POST" class="form">
12
+ <%- f.field('visual_settings.layout', { as: 'select', required: true, options: [
13
+ ["default", "Default"],
14
+ ["landing", "Landing page"],
15
+ ["headless", "Headless"],
16
+ ] }) %>
17
+
18
+ <div class="form-actions">
19
+ <div class="btn-toolbar">
20
+ <%- f.submit('Save changes', { class: 'btn-primary' }) %>
21
+ <a href="<%= `/${_context.locale}/articles/${visual_settings.originalId}` %>" class="btn-secondary">Back</a>
22
+ </div>
23
+ </div>
24
+ </form>
25
+ <% }) %>
26
+ </div>
27
+
28
+ <div>
29
+ <div class="help">
30
+ <div class="mb-2 flex items-center gap-1">
31
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
32
+ <path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
33
+ </svg>
34
+
35
+ <h3 class="heading-3">Contextual help</h3>
36
+ </div>
37
+
38
+ <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Tempore nostrum fugit sint sunt distinctio ut cum, corrupti ad error, quo labore minima. Reprehenderit quasi maxime repudiandae voluptatum commodi saepe molestiae?</p>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </div>
@@ -0,0 +1,86 @@
1
+ <% form_for({ article }, errors, (f) => { %>
2
+ <form action="<%= article?.id ? `/${_context.locale}/articles/${article.id}` : `/${_context.locale}/articles` %>" method="post" class="form">
3
+ <%- f.field('article.title', { required: true }) %>
4
+ <%- f.field('article.slug', { label: "URL", required: true }) %>
5
+ <%- f.field('article.publish_at', { required: true, type: 'datetime-local' }) %>
6
+
7
+ <%- f.field('article.visibility', { as: 'select', required: true, options: visibilityOptions }) %>
8
+
9
+ <%- f.field('article.username', { required: true }) %>
10
+ <%- f.field('article.password', { required: true }) %>
11
+
12
+ <%- f.field('article.layout', { as: 'select', required: true, options: [
13
+ ["default", "Default"],
14
+ ["landing", "Landing page"],
15
+ ["headless", "Headless"],
16
+ ] }) %>
17
+
18
+ <%- f.field('article.meta_title', { required: true }) %>
19
+ <%- f.field('article.meta_description', { as: "textarea", required: true }) %>
20
+
21
+ <raketa-image-picker
22
+ name="article[meta_image]"
23
+ value="<%= JSON.stringify(article.meta_image) %>"
24
+ ></raketa-image-picker>
25
+
26
+ <%- f.field('article.tags', { as: 'checkbox_group', required: true, options: tags.map(tag => [tag.id, tag.name]) }) %>
27
+
28
+ <div class="form-actions">
29
+ <div class="btn-toolbar">
30
+ <%- f.submit('Save record', { class: 'btn-primary' }) %>
31
+
32
+ <a href="<%= `/${_context.locale}/articles` %>" class="btn-secondary">Back to list</a>
33
+ </div>
34
+
35
+ <% if (article?.id) {%>
36
+ <button hx-delete="<%= `/${_context.locale}/articles/${article.id}` %>" hx-confirm="Are you sure?" class="btn-danger has-icon">
37
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
38
+ <path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
39
+ </svg>
40
+
41
+ Delete record
42
+ </button>
43
+ <% } %>
44
+ </div>
45
+ </form>
46
+ <% }) %>
47
+
48
+ <div>
49
+ <div class="help">
50
+ <div class="flex items-center gap-1 mb-2">
51
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
52
+ <path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
53
+ </svg>
54
+
55
+ <h3 class="heading-3">Contextual help</h3>
56
+ </div>
57
+
58
+ <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Tempore nostrum fugit sint sunt distinctio ut cum, corrupti ad error, quo labore minima. Reprehenderit quasi maxime repudiandae voluptatum commodi saepe molestiae?</p>
59
+ </div>
60
+ </div>
61
+
62
+ <script>
63
+ function onVisibilityChange(event) {
64
+ const newValue = event.target.value;
65
+
66
+ if (newValue === "protected") {
67
+ document.getElementById("f_username").parentElement.style.display = "block";
68
+ document.getElementById("f_username").required = true;
69
+ document.getElementById("f_password").parentElement.style.display = "block";
70
+ document.getElementById("f_password").required = true;
71
+ } else {
72
+ document.getElementById("f_username").parentElement.style.display = "none";
73
+ document.getElementById("f_username").required = false;
74
+ document.getElementById("f_password").parentElement.style.display = "none";
75
+ document.getElementById("f_password").required = false;
76
+ }
77
+ }
78
+
79
+ onVisibilityChange({
80
+ target: {
81
+ value: '<%= article.visibility %>'
82
+ }
83
+ })
84
+
85
+ document.getElementById("f_visibility").addEventListener("change", onVisibilityChange)
86
+ </script>
@@ -0,0 +1,9 @@
1
+ <div class="page-wrapper">
2
+ <div class="page-title">
3
+ <h1 class="heading-1">Create article</h1>
4
+ </div>
5
+
6
+ <div class="grid gap-4 md:grid-cols-3">
7
+ <%- include('_form.ejs') %>
8
+ </div>
9
+ </div>
@@ -0,0 +1,55 @@
1
+ <div class="page-wrapper">
2
+ <div class="page-title">
3
+ <h1 class="heading-1">Delete an article</h1>
4
+ </div>
5
+
6
+ <div class="grid gap-4 md:grid-cols-3">
7
+ <div>
8
+ <% form_for({ deleteAction }, errors, (f) => { %>
9
+ <form action="<%= `/${_context.locale}/articles/${deleteAction.id}/deletes` %>" method="POST" class="form">
10
+ <%- f.field('deleteAction.redirect', { as: 'select', required: true,options: [['301', 'Redirect to another page'], ['404', 'Return 404'],['410', 'Return 410']] }) %>
11
+ <%- f.field('deleteAction.destination', {label: 'Destination URL', required: true}) %>
12
+
13
+ <div class="form-actions">
14
+ <div class="btn-toolbar">
15
+ <%- f.submit('Proceed', { class: 'btn-primary' }) %>
16
+ <a href="<%=`/${_context.locale}/articles/${deleteAction.id}`%>" class="btn-secondary">Back</a>
17
+ </div>
18
+ </div>
19
+ </form>
20
+ <% }) %>
21
+ </div>
22
+
23
+ <div>
24
+ <div class="help">
25
+ <div class="mb-2 flex items-center gap-1">
26
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
27
+ <path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
28
+ </svg>
29
+
30
+ <h3 class="heading-3">Contextual help</h3>
31
+ </div>
32
+
33
+ <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Tempore nostrum fugit sint sunt distinctio ut cum, corrupti ad error, quo labore minima. Reprehenderit quasi maxime repudiandae voluptatum commodi saepe molestiae?</p>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </div>
38
+
39
+ <script>
40
+ function onDestinationChange(event) {
41
+ const newValue = event.target.value;
42
+ const requiresRedirect = newValue === "301"
43
+
44
+ document.getElementById("f_destination").parentElement.style.display = requiresRedirect ? "block" : "none";
45
+ document.getElementById("f_destination").required = requiresRedirect;
46
+ }
47
+
48
+ onDestinationChange({
49
+ target: {
50
+ value: '<%= deleteAction.redirect %>'
51
+ }
52
+ })
53
+
54
+ document.getElementById("f_redirect").addEventListener("change", onDestinationChange)
55
+ </script>
@@ -0,0 +1,3 @@
1
+ <h1>Hello from edit form <%= id %></h1>
2
+
3
+ <%- include('_form.ejs') %>
@@ -0,0 +1,161 @@
1
+ <div class="page-wrapper">
2
+ <div class="page-title">
3
+ <h1 class="heading-1">Articles</h1>
4
+
5
+ <div class="actions-toolbar">
6
+ <a href="<%= `/${_context.locale}/articles/new` %>" class="btn-primary">
7
+ Create new
8
+ </a>
9
+
10
+ <button type="button" data-toggle-sidebar="1" class="p-2.5 rounded hover:bg-neutral-200 transition-all duration-300 ease-in-out dark:hover:bg-neutral-700">
11
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
12
+ <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
13
+ </svg>
14
+ </button>
15
+ </div>
16
+ </div>
17
+
18
+ <table class="data-grid">
19
+ <thead>
20
+ <tr>
21
+ <th><%- sort_button("Title", "title", _context) %></th>
22
+ <th>Visibility</th>
23
+ <th><%- sort_button("Last change", "updated_at", _context) %></th>
24
+ <th>Translations</th>
25
+ </tr>
26
+ </thead>
27
+
28
+ <tbody>
29
+ <% items.list.forEach((item, idx)=> { %>
30
+ <tr>
31
+ <td class="w-1/2">
32
+ <a href="<%= `/${_context.locale}/articles/${item.id}` %>" class="btn-text mr-1">
33
+ <%= item.title %>
34
+ </a>
35
+ <% if (item.revisionsCount > 0) { %>
36
+ <span class="bg-orange-100 chip text-orange-700 dark:bg-orange-900/50 dark:text-orange-300"><%= item.revisionsCount !== 1 ? `${item.revisionsCount} open revisions` : 'Open revision' %></span>
37
+ <% } %>
38
+
39
+ <div class="mt-1">
40
+ <a href="<%= `https://localhost:3000/${_context.locale}/${item.slug}` %>" class="body-3 text-raketa-foreground/50" target="_blank">
41
+ /<%= item.slug %>
42
+ </a>
43
+ </div>
44
+ </td>
45
+
46
+ <td>
47
+ <div class="flex itemx-center gap-1 whitespace-nowrap">
48
+ <span class="text-raketa-foreground/30" title="<%= visibilityOptions.find(opt => opt[0] === item.visibility)[1] %>">
49
+ <% if (item.visibility === 'public') { %>
50
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
51
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418" />
52
+ </svg>
53
+
54
+ <% } %>
55
+ <% if (item.visibility === 'private') { %>
56
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
57
+ <path d="M8.5 4.5a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0ZM10.9 12.006c.11.542-.348.994-.9.994H2c-.553 0-1.01-.452-.902-.994a5.002 5.002 0 0 1 9.803 0ZM14.002 12h-1.59a2.556 2.556 0 0 0-.04-.29 6.476 6.476 0 0 0-1.167-2.603 3.002 3.002 0 0 1 3.633 1.911c.18.522-.283.982-.836.982ZM12 8a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z" />
58
+ </svg>
59
+ <% } %>
60
+ <% if (item.visibility === 'unlisted') { %>
61
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
62
+ <path fill-rule="evenodd" d="M3.28 2.22a.75.75 0 0 0-1.06 1.06l10.5 10.5a.75.75 0 1 0 1.06-1.06l-1.322-1.323a7.012 7.012 0 0 0 2.16-3.11.87.87 0 0 0 0-.567A7.003 7.003 0 0 0 4.82 3.76l-1.54-1.54Zm3.196 3.195 1.135 1.136A1.502 1.502 0 0 1 9.45 8.389l1.136 1.135a3 3 0 0 0-4.109-4.109Z" clip-rule="evenodd" />
63
+ <path d="m7.812 10.994 1.816 1.816A7.003 7.003 0 0 1 1.38 8.28a.87.87 0 0 1 0-.566 6.985 6.985 0 0 1 1.113-2.039l2.513 2.513a3 3 0 0 0 2.806 2.806Z" />
64
+ </svg>
65
+ <% } %>
66
+ <% if (item.visibility === 'protected') { %>
67
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
68
+ <path fill-rule="evenodd" d="M8 1a3.5 3.5 0 0 0-3.5 3.5V7A1.5 1.5 0 0 0 3 8.5v5A1.5 1.5 0 0 0 4.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 11.5 7V4.5A3.5 3.5 0 0 0 8 1Zm2 6V4.5a2 2 0 1 0-4 0V7h4Z" clip-rule="evenodd" />
69
+ </svg>
70
+ <% } %>
71
+ </span>
72
+
73
+ <%= visibilityOptions.find(opt => opt[0] === item.visibility)[1] %>
74
+ </div>
75
+ </td>
76
+
77
+ <td>
78
+ <div class="body-3 text-raketa-foreground/50">
79
+ <div class="flex items-center gap-1 mb-0.5">
80
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4 text-raketa-foreground">
81
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
82
+ </svg>
83
+
84
+ Jan 20, 2024
85
+ </div>
86
+
87
+ <div class="flex items-center gap-1" title="Yana Dimitrova">
88
+ <img src="https://lh3.googleusercontent.com/a/ACg8ocLRrXdEsbxWg9AMWcgEY9Gfs75-xp7dYsnanB9-u-ZS3A=s96-c" class="size-4 rounded-full" alt="Yana Dimitrova">
89
+ Yana D.
90
+ </div>
91
+ </div>
92
+ </td>
93
+
94
+ <td>
95
+ <div class="flex flex-wrap items-center gap-1">
96
+ <% Object.keys(_context.locales).forEach(key => { %>
97
+ <% const { name, _code, flag} = _context.locales[key] %>
98
+ <% if (key === _context.locale) { %>
99
+ <img title="<%= name %>" src="<%= flag %>" alt="<%= name %>" class="size-6 rounded-full border dark:border-neutral-700" />
100
+ <% } else if (item.translations[key]) { %>
101
+ <a href="<%= `/${key}/articles/${item.translations[key].id}` %>">
102
+ <img title="<%= name %>" src="<%= flag %>" alt="<%= name %>" class="size-6 rounded-full border dark:border-neutral-700" />
103
+ </a>
104
+ <% } else { %>
105
+ <a href="<%= `/${_context.locale}/articles/${item.id}/translations/new?target_locale=${key}` %>">
106
+ <img title="<%= name %>" src="<%= flag %>" alt="<%= name %>" class="<%= [" transition-all ease-in-out duration-300 size-6 rounded-full border hover:opacity-100 dark:border-neutral-700", key === _context.locale ? "opacity-100" : "opacity-10"].join(" ") %>" />
107
+ </a>
108
+ <% } %>
109
+ <% }); %>
110
+ </div>
111
+ </td>
112
+ </tr>
113
+ <% }); %>
114
+ </tbody>
115
+ </table>
116
+
117
+ <%- pagination(items.pagination, _context) %>
118
+ </div>
119
+
120
+ <div data-sidebar="1" class="search-sidebar <%= Object.keys(_context.query).some(k => k.startsWith('f:')) ? '' : 'hidden' %>">
121
+ <span class="heading-2">Search by</span>
122
+
123
+ <form action="<%= `/${_context.locale}/articles` %>" method="GET" class="form mt-4">
124
+ <input type="hidden" name="order_direction" value="<%= _context.query.order_direction %>" />
125
+ <input type="hidden" name="order_by" value="<%= _context.query.order_by %>" />
126
+
127
+ <div class="mb-4">
128
+ <label for="keyword" class="font-semibold hover:cursor-pointer body-3">Keyword</label>
129
+ <input type="text" name="f:keyword" value="<%= _context.query["f:keyword"] %>" />
130
+ </div>
131
+
132
+ <div class="mb-4">
133
+ <label for="" class="font-semibold hover:cursor-pointer body-3">Tags</label>
134
+ <div class="checkbox-group mb-4">
135
+ <% tags.forEach((item,idx)=> { %>
136
+ <div class="flex items-center gap-1">
137
+ <% if (Array.from(_context.query["f:tags"] || []).includes(item.id)) { %>
138
+ <input type="checkbox" name="f:tags" id='tag-<%= item.id %>' checked value="<%= item.id %>" class="hover:cursor-pointer">
139
+ <% } else { %>
140
+ <input type="checkbox" name="f:tags" id='tag-<%= item.id %>' value="<%= item.id %>" class="hover:cursor-pointer">
141
+ <% } %>
142
+ <label for='tag-<%= item.id %>' class="body-2 hover:cursor-pointer"><%= item.name %></label>
143
+ </div>
144
+ <% }); %>
145
+ </div>
146
+ </div>
147
+
148
+ <div class="btn-toolbar">
149
+ <button type="submit" class="btn-primary">Search</button>
150
+ <a href="<%= `/${_context.locale}/articles` %>" class="btn-text">Clear search</a>
151
+ </div>
152
+ </form>
153
+ </div>
154
+
155
+ <script>
156
+ const sidebar = document.querySelector("[data-sidebar]");
157
+ const searchToggle = document.querySelector('[data-toggle-sidebar]');
158
+ searchToggle.addEventListener('click', () => {
159
+ sidebar.classList.toggle('hidden');
160
+ });
161
+ </script>