@rmdes/indiekit-frontend 1.0.0-beta.25

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 (213) hide show
  1. package/README.md +10 -0
  2. package/assets/app-icon-any.svg +5 -0
  3. package/assets/app-icon-maskable.svg +5 -0
  4. package/assets/icon.svg +3 -0
  5. package/assets/not-found.svg +7 -0
  6. package/assets/offline.svg +7 -0
  7. package/assets/plug-in.svg +4 -0
  8. package/components/actions/macro.njk +3 -0
  9. package/components/actions/styles.css +19 -0
  10. package/components/actions/template.njk +19 -0
  11. package/components/add-another/index.js +170 -0
  12. package/components/add-another/macro.njk +3 -0
  13. package/components/add-another/styles.css +63 -0
  14. package/components/add-another/template.njk +39 -0
  15. package/components/app/styles.css +45 -0
  16. package/components/authorize/macro.njk +3 -0
  17. package/components/authorize/styles.css +23 -0
  18. package/components/authorize/template.njk +14 -0
  19. package/components/avatar/macro.njk +3 -0
  20. package/components/avatar/styles.css +23 -0
  21. package/components/avatar/template.njk +1 -0
  22. package/components/back-link/macro.njk +3 -0
  23. package/components/back-link/styles.css +46 -0
  24. package/components/back-link/template.njk +5 -0
  25. package/components/badge/macro.njk +3 -0
  26. package/components/badge/styles.css +85 -0
  27. package/components/badge/template.njk +7 -0
  28. package/components/bookmarklet/macro.njk +3 -0
  29. package/components/bookmarklet/styles.css +18 -0
  30. package/components/bookmarklet/template.njk +1 -0
  31. package/components/button/macro.njk +3 -0
  32. package/components/button/styles.css +108 -0
  33. package/components/button/template.njk +17 -0
  34. package/components/card/macro.njk +3 -0
  35. package/components/card/styles.css +93 -0
  36. package/components/card/template.njk +46 -0
  37. package/components/card-grid/macro.njk +3 -0
  38. package/components/card-grid/styles.css +9 -0
  39. package/components/card-grid/template.njk +8 -0
  40. package/components/character-count/index.js +185 -0
  41. package/components/character-count/macro.njk +3 -0
  42. package/components/character-count/styles.css +3 -0
  43. package/components/character-count/template.njk +14 -0
  44. package/components/checkboxes/index.js +130 -0
  45. package/components/checkboxes/macro.njk +3 -0
  46. package/components/checkboxes/styles.css +96 -0
  47. package/components/checkboxes/template.njk +96 -0
  48. package/components/details/macro.njk +3 -0
  49. package/components/details/styles.css +24 -0
  50. package/components/details/template.njk +12 -0
  51. package/components/error-message/macro.njk +3 -0
  52. package/components/error-message/styles.css +5 -0
  53. package/components/error-message/template.njk +9 -0
  54. package/components/error-summary/index.js +147 -0
  55. package/components/error-summary/macro.njk +3 -0
  56. package/components/error-summary/styles.css +24 -0
  57. package/components/error-summary/template.njk +27 -0
  58. package/components/event-duration/index.js +26 -0
  59. package/components/event-duration/styles.css +7 -0
  60. package/components/field/macro.njk +3 -0
  61. package/components/field/styles.css +14 -0
  62. package/components/field/template.njk +4 -0
  63. package/components/fieldset/macro.njk +3 -0
  64. package/components/fieldset/styles.css +27 -0
  65. package/components/fieldset/template.njk +13 -0
  66. package/components/file-input/index.js +123 -0
  67. package/components/file-input/macro.njk +3 -0
  68. package/components/file-input/styles.css +3 -0
  69. package/components/file-input/template.njk +45 -0
  70. package/components/footer/macro.njk +3 -0
  71. package/components/footer/styles.css +18 -0
  72. package/components/footer/template.njk +8 -0
  73. package/components/geo-input/index.js +105 -0
  74. package/components/geo-input/macro.njk +3 -0
  75. package/components/geo-input/styles.css +3 -0
  76. package/components/geo-input/template.njk +35 -0
  77. package/components/header/macro.njk +3 -0
  78. package/components/header/styles.css +60 -0
  79. package/components/header/template.njk +11 -0
  80. package/components/heading/macro.njk +3 -0
  81. package/components/heading/styles.css +24 -0
  82. package/components/heading/template.njk +20 -0
  83. package/components/hint/macro.njk +3 -0
  84. package/components/hint/styles.css +10 -0
  85. package/components/hint/template.njk +3 -0
  86. package/components/icon/styles.css +14 -0
  87. package/components/input/macro.njk +3 -0
  88. package/components/input/styles.css +111 -0
  89. package/components/input/template.njk +45 -0
  90. package/components/label/macro.njk +3 -0
  91. package/components/label/styles.css +4 -0
  92. package/components/label/template.njk +4 -0
  93. package/components/logo/macro.njk +3 -0
  94. package/components/logo/styles.css +24 -0
  95. package/components/logo/template.njk +3 -0
  96. package/components/main/styles.css +14 -0
  97. package/components/mention/macro.njk +3 -0
  98. package/components/mention/styles.css +9 -0
  99. package/components/mention/template.njk +6 -0
  100. package/components/navigation/macro.njk +3 -0
  101. package/components/navigation/styles.css +19 -0
  102. package/components/navigation/template.njk +11 -0
  103. package/components/notification-banner/index.js +24 -0
  104. package/components/notification-banner/macro.njk +3 -0
  105. package/components/notification-banner/styles.css +49 -0
  106. package/components/notification-banner/template.njk +20 -0
  107. package/components/pagination/macro.njk +3 -0
  108. package/components/pagination/styles.css +91 -0
  109. package/components/pagination/template.njk +42 -0
  110. package/components/progress/macro.njk +3 -0
  111. package/components/progress/styles.css +38 -0
  112. package/components/progress/template.njk +10 -0
  113. package/components/prose/macro.njk +3 -0
  114. package/components/prose/styles.css +12 -0
  115. package/components/prose/template.njk +3 -0
  116. package/components/radios/index.js +83 -0
  117. package/components/radios/macro.njk +3 -0
  118. package/components/radios/styles.css +104 -0
  119. package/components/radios/template.njk +87 -0
  120. package/components/section/macro.njk +3 -0
  121. package/components/section/styles.css +12 -0
  122. package/components/section/template.njk +19 -0
  123. package/components/select/macro.njk +3 -0
  124. package/components/select/styles.css +37 -0
  125. package/components/select/template.njk +48 -0
  126. package/components/share-preview/index.js +54 -0
  127. package/components/share-preview/macro.njk +3 -0
  128. package/components/share-preview/styles.css +36 -0
  129. package/components/share-preview/template.njk +14 -0
  130. package/components/skip-link/macro.njk +3 -0
  131. package/components/skip-link/styles.css +12 -0
  132. package/components/skip-link/template.njk +3 -0
  133. package/components/summary/macro.njk +3 -0
  134. package/components/summary/styles.css +60 -0
  135. package/components/summary/template.njk +19 -0
  136. package/components/tag/macro.njk +3 -0
  137. package/components/tag/styles.css +14 -0
  138. package/components/tag/template.njk +5 -0
  139. package/components/tag-input/index.js +69 -0
  140. package/components/tag-input/macro.njk +3 -0
  141. package/components/tag-input/sanitizer.js +27 -0
  142. package/components/tag-input/styles.css +161 -0
  143. package/components/tag-input/template.njk +29 -0
  144. package/components/textarea/index.js +298 -0
  145. package/components/textarea/macro.njk +3 -0
  146. package/components/textarea/styles.css +42 -0
  147. package/components/textarea/template.njk +40 -0
  148. package/components/user/macro.njk +3 -0
  149. package/components/user/styles.css +20 -0
  150. package/components/user/template.njk +15 -0
  151. package/components/warning-text/macro.njk +3 -0
  152. package/components/warning-text/styles.css +15 -0
  153. package/components/warning-text/template.njk +8 -0
  154. package/components/widget/macro.njk +3 -0
  155. package/components/widget/styles.css +28 -0
  156. package/components/widget/template.njk +20 -0
  157. package/index.js +10 -0
  158. package/layouts/default.njk +122 -0
  159. package/layouts/document.njk +15 -0
  160. package/layouts/error.njk +19 -0
  161. package/layouts/form.njk +22 -0
  162. package/lib/esbuild.js +22 -0
  163. package/lib/filters/index.js +10 -0
  164. package/lib/filters/locale.js +17 -0
  165. package/lib/filters/string.js +29 -0
  166. package/lib/filters/url.js +69 -0
  167. package/lib/globals/attributes.js +23 -0
  168. package/lib/globals/classes.js +19 -0
  169. package/lib/globals/error-list.js +21 -0
  170. package/lib/globals/field-data.js +20 -0
  171. package/lib/globals/icon.js +80 -0
  172. package/lib/globals/index.js +12 -0
  173. package/lib/globals/item-id.js +17 -0
  174. package/lib/globals/summary-rows.js +39 -0
  175. package/lib/lightningcss.js +41 -0
  176. package/lib/markdown-it.js +27 -0
  177. package/lib/nunjucks.js +43 -0
  178. package/lib/serviceworker.js +240 -0
  179. package/lib/sharp.js +39 -0
  180. package/lib/utils/theme.js +115 -0
  181. package/lib/utils/wrap-element.js +11 -0
  182. package/locales/de.json +60 -0
  183. package/locales/en.json +60 -0
  184. package/locales/es-419.json +60 -0
  185. package/locales/es.json +60 -0
  186. package/locales/fr.json +60 -0
  187. package/locales/hi.json +60 -0
  188. package/locales/id.json +60 -0
  189. package/locales/it.json +60 -0
  190. package/locales/nl.json +60 -0
  191. package/locales/pl.json +60 -0
  192. package/locales/pt-BR.json +60 -0
  193. package/locales/pt.json +60 -0
  194. package/locales/sr.json +60 -0
  195. package/locales/sv.json +60 -0
  196. package/locales/zh-Hans-CN.json +60 -0
  197. package/package.json +64 -0
  198. package/scripts/app.js +23 -0
  199. package/styles/app.css +73 -0
  200. package/styles/base/embedded.css +29 -0
  201. package/styles/base/forms.css +55 -0
  202. package/styles/base/grouping.css +43 -0
  203. package/styles/base/interactive.css +19 -0
  204. package/styles/base/sections.css +59 -0
  205. package/styles/base/tables.css +30 -0
  206. package/styles/base/text.css +71 -0
  207. package/styles/config/custom-properties.css +211 -0
  208. package/styles/scopes/flow.css +114 -0
  209. package/styles/utilities/container.css +6 -0
  210. package/styles/utilities/visually-hidden.css +10 -0
  211. package/styles/vendor/codemirror.css +116 -0
  212. package/styles/vendor/easy-markdown-editor.css +219 -0
  213. package/styles/vendor/markdown-it-prism.css +79 -0
@@ -0,0 +1,20 @@
1
+ {% from "../actions/macro.njk" import actions with context %}
2
+ {% from "../prose/macro.njk" import prose with context %}
3
+ {% set id = opts.id or opts.title | slugify %}
4
+ <section class="{{ classes("widget", opts) }}"
5
+ {%- if opts.title %} aria-labelledby="{{ id }}"{% endif %}>
6
+ {% if opts.title or opts.actions %}
7
+ <header class="widget__header">
8
+ {% if opts.title %}
9
+ <h2 class="widget__title" id="{{ id }}">
10
+ <img class="icon icon--rounded" src="{{ opts.image }}" alt="" onerror="this.src='/assets/plug-in.svg'" height="20" width="20">
11
+ {{- opts.title | safe }}
12
+ </h2>
13
+ {% endif %}
14
+ {{ actions({ items: opts.actions }) | indent(4) if opts.actions.length }}
15
+ </header>
16
+ {% endif %}
17
+ <div class="widget__main">
18
+ {{ caller() if caller else prose(opts) }}
19
+ </div>
20
+ </section>
package/index.js ADDED
@@ -0,0 +1,10 @@
1
+ import { fileURLToPath } from "node:url";
2
+
3
+ export const assetsPath = fileURLToPath(new URL("assets", import.meta.url));
4
+ export { appIcon, shortcutIcon } from "./lib/sharp.js";
5
+ export { templates } from "./lib/nunjucks.js";
6
+ export { scripts } from "./lib/esbuild.js";
7
+ export { styles } from "./lib/lightningcss.js";
8
+ export { getBackgroundColor, getThemeColor } from "./lib/utils/theme.js";
9
+
10
+ export { tagInputSanitizer } from "./components/tag-input/sanitizer.js";
@@ -0,0 +1,122 @@
1
+ {% from "add-another/macro.njk" import addAnother with context %}
2
+ {% from "avatar/macro.njk" import avatar with context %}
3
+ {% from "back-link/macro.njk" import backLink with context %}
4
+ {% from "badge/macro.njk" import badge with context %}
5
+ {% from "bookmarklet/macro.njk" import bookmarklet with context %}
6
+ {% from "button/macro.njk" import button with context %}
7
+ {% from "card/macro.njk" import card with context %}
8
+ {% from "card-grid/macro.njk" import cardGrid with context %}
9
+ {% from "checkboxes/macro.njk" import checkboxes with context %}
10
+ {% from "details/macro.njk" import details with context %}
11
+ {% from "error-summary/macro.njk" import errorSummary with context %}
12
+ {% from "fieldset/macro.njk" import fieldset with context %}
13
+ {% from "file-input/macro.njk" import fileInput with context %}
14
+ {% from "footer/macro.njk" import footer with context %}
15
+ {% from "geo-input/macro.njk" import geoInput with context %}
16
+ {% from "header/macro.njk" import header with context %}
17
+ {% from "heading/macro.njk" import heading with context %}
18
+ {% from "hint/macro.njk" import hint with context %}
19
+ {% from "input/macro.njk" import input with context %}
20
+ {% from "mention/macro.njk" import mention with context %}
21
+ {% from "notification-banner/macro.njk" import notificationBanner with context %}
22
+ {% from "pagination/macro.njk" import pagination with context %}
23
+ {% from "prose/macro.njk" import prose with context %}
24
+ {% from "radios/macro.njk" import radios with context %}
25
+ {% from "section/macro.njk" import section with context %}
26
+ {% from "select/macro.njk" import select with context %}
27
+ {% from "skip-link/macro.njk" import skipLink with context %}
28
+ {% from "summary/macro.njk" import summary with context %}
29
+ {% from "tag/macro.njk" import tag with context %}
30
+ {% from "tag-input/macro.njk" import tagInput with context %}
31
+ {% from "textarea/macro.njk" import textarea with context %}
32
+ {% from "user/macro.njk" import user with context %}
33
+ {% from "warning-text/macro.njk" import warningText with context %}
34
+ {% from "widget/macro.njk" import widget with context %}
35
+ {% set appClasses = "app" + (" " + appClasses if appClasses) + (" app--minimalui" if minimalui) %}
36
+ {% set mainClasses = "main" + (" " + mainClasses if mainClasses) %}
37
+ <!DOCTYPE html>
38
+ <html lang="{{ application.localeUsed }}" dir="ltr"{% if application.themeColorScheme !== "automatic" %} data-color-scheme="{{ application.themeColorScheme }}"{% endif %}>
39
+ <head>
40
+ <meta charset="utf-8">
41
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
42
+
43
+ <title>{% if errors %}Error: {% endif %}{{ title + " - " if title }}{{ application.name }}</title>
44
+
45
+ <script async defer src="{{ application.url }}{{ application.jsPath }}" type="module"></script>
46
+
47
+ <style>
48
+ :root {
49
+ {{- themeCustomProperties(application.themeColor) | indent(6) }}
50
+ }
51
+ </style>
52
+
53
+ <link rel="stylesheet" href="{{ application.url }}{{ application.cssPath }}">
54
+ <link rel="alternate" href="{{ application.url }}/feed.jf2" type="application/jf2feed+json" title="JF2 Feed">
55
+ <link rel="apple-touch-icon" href="{{ application.url }}{{ assetPath | default("/assets") }}/app-icon-180-any.png">
56
+ <link rel="icon" href="{{ application.url }}{{ assetPath | default("/assets") }}/icon.svg">
57
+ <link rel="manifest" href="{{ application.url }}/app.webmanifest">
58
+
59
+ <meta name="supported-color-schemes" content="light dark">
60
+ <meta name="theme-color" content="{{ themeColor(application.themeColor) }}" media="(prefers-color-scheme: light)">
61
+ <meta name="theme-color" content="{{ themeColor(application.themeColor, "dark") }}" media="(prefers-color-scheme: dark)">
62
+ </head>
63
+
64
+ <body class="{{ appClasses }}">
65
+ <script>document.body.classList.add("js-enabled")</script>
66
+ {{ skipLink() }}
67
+
68
+ {% block header %}
69
+ {{ header({
70
+ url: application.url,
71
+ name: application.name,
72
+ navigation: {
73
+ items: application.navigation | rejectattr("secondary"),
74
+ label: "site"
75
+ }
76
+ }) if not minimalui }}
77
+ {% endblock %}
78
+
79
+ <main class="{{ mainClasses }}" id="main">
80
+ {{- notificationBanner({
81
+ type: ("error" if error) or ("success" if success),
82
+ text: (error if error) or (success if success) or notice,
83
+ details: error.stack if error
84
+ }) if error or success or notice }}
85
+ {{- backLink({
86
+ href: back.href or back,
87
+ text: back.text
88
+ }) if back }}
89
+ {% block main %}
90
+ {% endblock %}
91
+ </main>
92
+
93
+ {% block footer %}
94
+ {{ footer({
95
+ logo: {
96
+ classes: "h-x-app",
97
+ href: "https://getindiekit.com",
98
+ src: application.url + assetPath | default("/assets") + "/icon.svg",
99
+ alt: "Indiekit"
100
+ },
101
+ navigation: {
102
+ items: application.navigation | selectattr("secondary"),
103
+ label: "secondary"
104
+ }
105
+ }) if not minimalui }}
106
+ {% endblock %}
107
+ <script type="module">
108
+ if (navigator.serviceWorker) {
109
+ window.addEventListener("load", () => {
110
+ navigator.serviceWorker.register("/serviceworker.js", {
111
+ scope: '/'
112
+ });
113
+ if (navigator.serviceWorker.controller) {
114
+ navigator.serviceWorker.controller.postMessage({
115
+ command: "trimCaches"
116
+ });
117
+ }
118
+ });
119
+ }
120
+ </script>
121
+ </body>
122
+ </html>
@@ -0,0 +1,15 @@
1
+ {% extends "default.njk" %}
2
+
3
+ {% block main %}
4
+ <article class="main__container -!-container">
5
+ {{ heading({
6
+ photo: photo,
7
+ parent: parent,
8
+ text: title,
9
+ actions: actions
10
+ }) if title }}
11
+
12
+ {% block content %}
13
+ {% endblock %}
14
+ </article>
15
+ {% endblock %}
@@ -0,0 +1,19 @@
1
+ {% extends "document.njk" %}
2
+
3
+ {% set appClasses = "app--minimalui" %}
4
+
5
+ {% block header %}
6
+ {{ header({
7
+ url: application.url,
8
+ name: application.name
9
+ }) }}
10
+ {% endblock %}
11
+
12
+ {% block content %}
13
+ {{ prose({ text: content | linkTo(uri) }) }}
14
+
15
+ {{ details({
16
+ summary: "Status code: <code>" + status + "</code>",
17
+ text: "```\n" + stack + "\n```"
18
+ }) }}
19
+ {% endblock %}
@@ -0,0 +1,22 @@
1
+ {% extends "default.njk" %}
2
+
3
+ {% block main %}
4
+ <form class="main__container -!-container" method="post" novalidate>
5
+ {{ errorSummary({
6
+ errorList: errorList(errors)
7
+ }) if errors }}
8
+
9
+ {% block form %}
10
+ {% call fieldset({
11
+ attributes: { "aria-label": title } if minimalui,
12
+ legend: heading({ text: title, parent: parent }) if not minimalui
13
+ }) -%}
14
+ {% block fieldset %}
15
+ {% endblock %}
16
+ {% endcall %}
17
+ {% endblock %}
18
+
19
+ {% block buttons %}
20
+ {% endblock %}
21
+ </form>
22
+ {% endblock %}
package/lib/esbuild.js ADDED
@@ -0,0 +1,22 @@
1
+ import { fileURLToPath } from "node:url";
2
+
3
+ import * as esbuild from "esbuild";
4
+
5
+ export const scripts = async () => {
6
+ const inputFile = fileURLToPath(
7
+ new URL("../scripts/app.js", import.meta.url),
8
+ );
9
+
10
+ const result = await esbuild.build({
11
+ entryPoints: [inputFile],
12
+ bundle: true,
13
+ legalComments: "none",
14
+ minify: true,
15
+ write: false,
16
+ });
17
+
18
+ const { contents } = result.outputFiles[0];
19
+ const code = new TextDecoder().decode(contents);
20
+
21
+ return code;
22
+ };
@@ -0,0 +1,10 @@
1
+ export {
2
+ excerpt,
3
+ formatDate as date,
4
+ formatZonedToLocalDate as localDate,
5
+ getCanonicalUrl as url,
6
+ slugify,
7
+ } from "@indiekit/util";
8
+ export { languageName, languageNativeName } from "./locale.js";
9
+ export { linkTo, markdown } from "./string.js";
10
+ export { friendlyUrl, imageUrl } from "./url.js";
@@ -0,0 +1,17 @@
1
+ import languages from "iso-639-1";
2
+
3
+ /**
4
+ * Get a language name
5
+ * @param {string} string - ISO 639-1 language code
6
+ * @returns {string} Native language name
7
+ * @example language('de') => Deutsch
8
+ */
9
+ export const languageName = (string) => languages.getName(string);
10
+
11
+ /**
12
+ * Get a language’s native name
13
+ * @param {string} string - ISO 639-1 language code
14
+ * @returns {string} Native language name
15
+ * @example language('de') => Deutsch
16
+ */
17
+ export const languageNativeName = (string) => languages.getNativeName(string);
@@ -0,0 +1,29 @@
1
+ import markdownIt from "../markdown-it.js";
2
+
3
+ /**
4
+ * Add Markdown link to text
5
+ * @param {string} string - Text
6
+ * @param {string} href - Hyperlink reference to link to
7
+ * @returns {string} Markdown string
8
+ */
9
+ export const linkTo = (string, href) => {
10
+ if (href) {
11
+ return `[${string}](${href})`;
12
+ }
13
+
14
+ return string;
15
+ };
16
+
17
+ /**
18
+ * Render Markdown string as HTML
19
+ * @param {string} string - Markdown
20
+ * @param {string} [value] - If 'inline', HTML rendered without paragraph tags
21
+ * @returns {string} HTML
22
+ */
23
+ export const markdown = (string, value) => {
24
+ if (value === "inline") {
25
+ return markdownIt.renderInline(string);
26
+ }
27
+
28
+ return markdownIt.render(string);
29
+ };
@@ -0,0 +1,69 @@
1
+ import path from "node:path";
2
+
3
+ /**
4
+ * Get friendly URL
5
+ * @param {string} string - URL or path
6
+ * @returns {string} Friendly URL
7
+ */
8
+ export const friendlyUrl = (string) => {
9
+ string = String(string);
10
+
11
+ try {
12
+ const { host, pathname } = new URL(string);
13
+
14
+ return pathname === "/" ? host : host + pathname;
15
+ } catch {
16
+ return string;
17
+ }
18
+ };
19
+
20
+ /**
21
+ * Get transformed image URL
22
+ * @param {string} string - Image URL (or path)
23
+ * @param {object} application - Application configuration
24
+ * @param {object} [kwargs] - Transform options
25
+ * @param {number} [kwargs.width] - Width of transformed image
26
+ * @param {number} [kwargs.height] - Height of transformed image
27
+ * @param {string} [kwargs.fit] - Resize crop method for transformed image
28
+ * @returns {string} Resized image URL
29
+ */
30
+ export const imageUrl = (string, application, kwargs) => {
31
+ // Don’t transform SVG images
32
+ if (string.includes(".svg")) {
33
+ return string;
34
+ }
35
+
36
+ // Resize dimensions
37
+ let resize;
38
+ if (kwargs?.width && kwargs?.height) {
39
+ resize = `s_${kwargs.width}x${kwargs.height}`;
40
+ } else if (kwargs?.width) {
41
+ resize = `w_${kwargs.width}`;
42
+ } else if (kwargs?.height) {
43
+ resize = `h_${kwargs.height}`;
44
+ }
45
+
46
+ // Resize crop method
47
+ const fit = kwargs?.fit ? `fit_${kwargs.fit}` : false;
48
+
49
+ // Build modification path
50
+ let modifierPath;
51
+ if (resize && fit) {
52
+ modifierPath = `${resize},${fit}`;
53
+ } else if (resize) {
54
+ modifierPath = resize;
55
+ } else if (fit) {
56
+ modifierPath = fit;
57
+ } else {
58
+ modifierPath = "_";
59
+ }
60
+
61
+ const imageResizePath = path.join(
62
+ application.imageEndpoint,
63
+ modifierPath,
64
+ encodeURIComponent(string),
65
+ );
66
+ const resizedURL = new URL(imageResizePath, application.url);
67
+
68
+ return resizedURL.href;
69
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Generate space-separated list of HTML attribute key values
3
+ * @param {object} attributes - Attributes
4
+ * @returns {string} Space-separated list of HTML attribute key values
5
+ */
6
+ export const attributes = (attributes) => {
7
+ let html = "";
8
+
9
+ for (const name in attributes) {
10
+ if (Object.prototype.hasOwnProperty.call(attributes, name)) {
11
+ if (!attributes[name]) {
12
+ return html;
13
+ }
14
+
15
+ html +=
16
+ typeof attributes[name] === "boolean"
17
+ ? ` ${name}`
18
+ : ` ${name}="${attributes[name]}"`;
19
+ }
20
+ }
21
+
22
+ return html;
23
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Generate space-separated list of class names
3
+ * @param {string} baseClass - Base class name
4
+ * @param {object} [options] - Component options
5
+ * @returns {string} Space-separated list of class names
6
+ */
7
+ export const classes = (baseClass, options = {}) => {
8
+ const classes = [baseClass];
9
+
10
+ if (options.errorMessage) {
11
+ classes.push(`${baseClass}--error`);
12
+ }
13
+
14
+ if (options.classes) {
15
+ classes.push(options.classes);
16
+ }
17
+
18
+ return classes.join(" ");
19
+ };
@@ -0,0 +1,21 @@
1
+ import { slugify } from "@indiekit/util";
2
+
3
+ /**
4
+ * Transform errors provided by express-validator into array that can be
5
+ * consumed by the error summary component
6
+ * @param {object} errorMap - Mapped error response from express-validator
7
+ * @returns {Array} List of errors
8
+ */
9
+ export const errorList = (errorMap) => {
10
+ // For each field that has errors, return only the first error
11
+ const errorList = [];
12
+ const fieldsWithErrors = Object.entries(errorMap);
13
+ for (const fieldError of fieldsWithErrors) {
14
+ errorList.push({
15
+ text: fieldError[1].msg,
16
+ href: `#${slugify(fieldError[1].path, { decamelize: true })}`,
17
+ });
18
+ }
19
+
20
+ return errorList;
21
+ };
@@ -0,0 +1,20 @@
1
+ import _ from "lodash";
2
+
3
+ /**
4
+ * Get field data
5
+ * @param {string} key - Key name
6
+ * @returns {object} Field data
7
+ */
8
+ export function fieldData(key) {
9
+ const { errors, properties } = this.ctx;
10
+ const errorData = _.get(errors, key);
11
+
12
+ return {
13
+ value: errorData ? errorData?.value : _.get(properties, key),
14
+ ...(errorData && {
15
+ errorMessage: {
16
+ text: errorData?.msg,
17
+ },
18
+ }),
19
+ };
20
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Render SVG icon
3
+ * @param {string} name - Icon name
4
+ * @param {string} [title] - Accessible title
5
+ * @returns {string|undefined} HTML
6
+ */
7
+ export const icon = (name, title) => {
8
+ const paths = {
9
+ article:
10
+ "M36 4H12c-4 0-8 4-8 8v24c0 5 4 8 8 8h24c5 0 8-3 8-8V12c0-4-3-8-8-8zM12 8h24c2 0 4 2 4 4v24c0 2-2 4-4 4H12c-2 0-4-2-4-4V12c0-2 2-4 4-4zm0 6h12v4H12v-4zm0 8h12v4H12v-4zm0 8h24v4H12v-4zm16-16h8v12h-8V14z",
11
+ audio:
12
+ "M36 4H12c-4 0-8 4-8 8v24c0 5 4 8 8 8h24c5 0 8-3 8-8V12c0-4-3-8-8-8zM12 8h24c2 0 4 2 4 4v24c0 2-2 4-4 4H12c-2 0-4-2-4-4V12c0-2 2-4 4-4zm10 17h-1c-3 0-5 2-5 5s2 5 5 5 5-2 5-5V20l6 1 1-3-11-4v11z",
13
+ bookmark:
14
+ "M20 2v2h16c4 0 8 3 8 8v24c0 5-3 8-8 8H12c-4 0-8-3-8-8V12c0-4 4-8 8-8V2h8zm16 6H20v20l-4-4-4 4V8c-2 0-4 2-4 4v24c0 2 2 4 4 4h24c2 0 4-2 4-4V12c0-2-2-4-4-4zm0 22v4H12v-4h24zm0-8v4H23v-4h13zm0-8v4H23v-4h13z",
15
+ checkin:
16
+ "M36 4H12c-4 0-8 4-8 8v20c0 5 4 8 8 8h5l7 8 7-8h5c5 0 8-3 8-8V12c0-4-3-8-8-8zm0 32h-7l-5 6-5-6h-7c-2 0-4-2-4-4V12c0-2 2-4 4-4h24c2 0 4 2 4 4v20c0 2-2 4-4 4zm-15-6l-7-7 3-3 4 4 10-9 3 3-13 12z",
17
+ createPost:
18
+ "M12 8h24a4 4 0 0 1 4 4v24a4 4 0 0 1-4 4H12a4 4 0 0 1-4-4V12a4 4 0 0 1 4-4Zm24-4H12c-4.5 0-8 3.5-8 8v24c0 4.5 3.5 8 8 8h24c4.5 0 8-3.5 8-8V12c0-4.5-3.5-8-8-8ZM26 14v8h8v4h-8v8h-4v-8h-8v-4h8v-8h4Z",
19
+ cross: "m45 6-3-3-18 18L6 3 3 6l18 18L3 42l3 3 18-18 18 18 3-3-18-18z",
20
+ delete:
21
+ "M9 40V10H6V6h11a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3h11v4h-3v30a4 4 0 0 1-4 4H13a4 4 0 0 1-4-4Zm26-30H13v29c0 .6.4 1 1 1h20c.6 0 1-.4 1-1V10ZM17.5 36h4V14h-4v22Zm9 0h4V14h-4v22Z",
22
+ event:
23
+ "M40 8h-2V4h-4v8h-2V8H18V4h-4v8h-2V8H8c-2 0-4 2-4 4v24c0 5 4 8 8 8h24c5 0 8-3 8-8V12c0-2-2-4-4-4zm-4 32H12c-2 0-4-2-4-4V16h32v20c0 2-2 4-4 4z",
24
+ jam: "M36 4H12c-4.5 0-8 3.5-8 8v24c0 4.5 3.5 8 8 8h24c4.5 0 8-3.5 8-8V12c0-4.5-3.5-8-8-8ZM12 8h24a4 4 0 0 1 4 4v24a4 4 0 0 1-4 4H12a4 4 0 0 1-4-4V12a4 4 0 0 1 4-4Zm23 21c0 2.8-2 5-4.9 5a5.1 5.1 0 0 1-5.1-5c0-2.8 2.3-5.1 5.1-5.1l.9.1v-7.5l-10 2V32c0 2.8-2 5-4.9 5a5.1 5.1 0 0 1-5.1-5c0-2.8 2.3-5.1 5.1-5.1l.9.1V14l18-3v18Z",
25
+ like: "M36 4H12c-4 0-8 4-8 8v24c0 5 4 8 8 8h24c5 0 8-3 8-8V12c0-4-3-8-8-8zM12 8h24c2 0 4 2 4 4v24c0 2-2 4-4 4H12c-2 0-4-2-4-4V12c0-2 2-4 4-4zm12 25s9-7 9-12c0-4-7-6-9 0-2-6-9-4-9 0 0 5 9 12 9 12z",
26
+ location: "M44 3 30 8 18 4 4 9v36l14-5 12 4 14-5V3ZM29 40l-11-4V8l11 4v28Z",
27
+ mention:
28
+ "M24 45C13 45 3 36 3 24S13 3 24 3s21 9 21 21c0 7-2.3 10.5-7 10.5-4.5 0-6-2.9-6.5-4C30.7 31.7 29 34 24 34a10 10 0 0 1 0-20c6 0 10 5 10 10v2c0 1 1 4.5 4 4.5 2 0 3-2.2 3-6.5 0-9-7-17-17-17S7 15 7 24s7 17 17 17h10v4H24Zm0-15a6 6 0 1 0 0-12 6 6 0 0 0 0 12Z",
29
+ next: "M28 7.5 40.5 20c3 3 3 5 0 8L28 40.5l-3-3L35.5 27l1.5-1H6v-4h31l-1.5-1L25 10.5l3-3Z",
30
+ note: "M12 8h24c2 0 4 2 4 4v24c0 2-2 4-4 4H12c-2 0-4-2-4-4V12c0-2 2-4 4-4zm24-4H12c-4 0-8 4-8 8v24c0 5 4 8 8 8h24c5 0 8-3 8-8V12c0-4-3-8-8-8zM12 14h24v4H12v-4zm0 8h24v4H12v-4zm0 8h18v4H12v-4z",
31
+ offline:
32
+ "M24 7a32 32 0 1 1-15.8 4.2l3.7 3.7a24.7 24.7 0 0 0-8.4 6.6L0 18v8.6a27 27 0 1 0 48 0V18l-3.5 3.5a27 27 0 0 0-25.1-9.1l-4.2-4.2A32 32 0 0 1 24 7Zm0 51a17 17 0 0 0 15.5-10h5.4A22 22 0 0 1 3 48h5.4A17 17 0 0 0 24 58Zm0-26a5 5 0 1 1 0 10 5 5 0 0 1 0-10Zm16-6-3.5 3.5-1.2-1.2-9.2-9.2a22 22 0 0 1 14 6.9Zm-22.9-6 4.1 4.1a17 17 0 0 0-9.7 5.3L8 26a22 22 0 0 1 9-6ZM2 5l3-3 41 41-3 3L2 5Z",
33
+ photo:
34
+ "M36 4H12c-4 0-8 4-8 8v24c0 5 4 8 8 8h24c5 0 8-3 8-8V12c0-4-3-8-8-8zM12 8h24c2 0 4 2 4 4v24c0 2-2 4-4 4H12c-2 0-4-2-4-4V12c0-2 2-4 4-4zm24 24l-9-14-7 11-3-5-5 8h24z",
35
+ previous:
36
+ "m23.8 10.5-3-3L8.2 20c-3 3-3 5 0 8l12.6 12.5 3-3L13.2 27l-1.4-1h31v-4h-31l1.4-1 10.6-10.5Z",
37
+ public:
38
+ "M24 45a20.4 20.4 0 0 1-14.8-6.2A21.2 21.2 0 0 1 3 24 20.3 20.3 0 0 1 9.2 9.2 21.5 21.5 0 0 1 24 3a20.3 20.3 0 0 1 14.8 6.2 22 22 0 0 1 4.5 6.7 20.3 20.3 0 0 1-4.5 23A21.2 21.2 0 0 1 24 45Zm-2-8a4 4 0 0 1-4-4v-3L7.6 19.6A17.2 17.2 0 0 0 22 40.9V37Zm19-13c0-10-8-15-11-15.9V9a3 3 0 0 1-3 3h-5v6a2 2 0 0 1-2 2h-4v4h8a6 6 0 0 1 6 6v2h4a2 2 0 0 1 2 2v2c3.3-3.3 5-7.3 5-12Z",
39
+ publicOff:
40
+ "M32.1 43.4a20.5 20.5 0 0 1-16.2 0A21.2 21.2 0 0 1 3 24a20.3 20.3 0 0 1 4.8-13.2L2 5l3-3 41 41-3 3-5.7-5.7a21.4 21.4 0 0 1-5.2 3ZM7.6 19.6A17.2 17.2 0 0 0 22 40.9V37a4 4 0 0 1-4-4v-3L7.6 19.6ZM24 3a20.3 20.3 0 0 1 14.8 6.2 22 22 0 0 1 4.5 6.7 20.3 20.3 0 0 1-1.4 19L39 32a17 17 0 0 0-9-24v1a3 3 0 0 1-3 3h-5v3l-8.9-8.9 2.8-1.4C18.4 3.6 21 3 24 3Zm-7 17h-1v4h5l-4-4Z",
41
+ reply:
42
+ "M12 48V38c-4 0-8-3-8-8V12c0-4 4-8 8-8h24c4 0 8 4 8 8v18c0 5-3 8-8 8H25L12 48zM8 12v18c0 2 2 4 4 4h4v6l7-6h13c2 0 4-2 4-4V12c0-2-2-4-4-4H12c-2 0-4 2-4 4z",
43
+ repost:
44
+ "M35 7c5 0 8 4 8 8v18c0 5-3 8-8 8h-7v6l-8-8 8-8v6h7c2 0 4-2 4-4V15c0-2-2-4-4-4h-4V7zM20 1l8 8-8 8v-6h-7c-2 0-4 2-4 4v18c0 2 2 4 4 4h4v4h-4c-4 0-8-3-8-8V15c0-4 4-8 8-8h7V1z",
45
+ rsvp: "M36 4c4 0 8 4 8 8v18c0 5-3 8-8 8H25L12 48V38c-4 0-8-3-8-8V12c0-4 4-8 8-8h24zm0 4H12c-2 0-4 2-4 4v18c0 2 2 4 4 4h4v6l7-6h13c2 0 4-2 4-4V12c0-2-2-4-4-4zm-5 6l3 3-13 12-7-7 3-3 4 4 10-9z",
46
+ syndicate:
47
+ "M9.2 9.2 12 12a17 17 0 0 0 0 24l-2.8 2.8a21 21 0 0 1 0-29.6ZM45 24a21 21 0 0 1-6.2 14.8L36 36a17 17 0 0 0 0-24l2.8-2.8A21 21 0 0 1 45 24Zm-30.2-9.2 2.8 2.8a9 9 0 0 0 0 12.8l-2.8 2.8a13 13 0 0 1 0-18.4ZM37 24a13 13 0 0 1-3.8 9.2l-2.8-2.8a9 9 0 0 0 0-12.8l2.8-2.8A13 13 0 0 1 37 24Zm-13-5a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z",
48
+ tick: "M16 40 3 27l3-3 10 10L42 8l3 3z",
49
+ undelete:
50
+ "M28 3a3 3 0 0 1 3 2.8V6h11v4h-3v30a4 4 0 0 1-4 4H13a4 4 0 0 1-4-4v-5h4v4c0 .6.4 1 1 1h20c.6 0 1-.4 1-1v-4c0-7.5-7-13-13-13s-9.5 2.5-12.5 7H18v4H5a2 2 0 0 1-2-2V18h4v7.5C12 19 17.5 18 22 18a17 17 0 0 1 13 6.4V10H13v7.5A12 12 0 0 0 9 20V10H6V6h11a3 3 0 0 1 3-3h8Z",
51
+ unknownPostType: "hello",
52
+ unlisted:
53
+ "m5 2 41 41-3 3L2 5l3-3Zm3.5 32.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5ZM28 35l4 4H14v-4h14ZM8.5 25.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5ZM19 26l4 4h-9v-4h5Zm23 0v4h-5l-4-4h9ZM8.5 16.5c.6 0 1 .2 1.5.5l.5.5a2.5 2.5 0 1 1-2-1ZM42 17v4H28l-4-4h18Zm0-9v4H19l-4-4h27Z",
54
+ updatePost:
55
+ "M12 8h24a4 4 0 0 1 4 4v24a4 4 0 0 1-4 4H12a4 4 0 0 1-4-4V12a4 4 0 0 1 4-4Zm28 9 4-4v12l-4 4V17ZM36 4H12c-4.5 0-8 3.5-8 8v24c0 4.5 3.5 8 8 8h24c4.5 0 8-3.5 8-8V12c0-4.5-3.5-8-8-8ZM12 14h24v4H12Zm0 8h22l-4 4H12v-4Zm29-3 3 3-14 14h-3v-3l14-14ZM27 30l-2 2v2H12v-4h15Zm19.5-13.5c.8.8.8 2.2 0 3L45 21l-3-3 1.5-1.5c.8-.8 2.2-.8 3 0Z",
56
+ uploadFile:
57
+ "M32.3 4H12c-4.5 0-8 3.5-8 8v24c0 4.5 3.5 8 8 8h24c4.5 0 8-3.5 8-8V15.7a4 4 0 0 0-1.2-2.9l-7.6-7.6A4 4 0 0 0 32.3 4ZM12 8h20v8h8v20a4 4 0 0 1-4 4H12a4 4 0 0 1-4-4V12a4 4 0 0 1 4-4Zm17.5 16 3-3-5-5c-3-3-4-3-7 0l-5 5 3 3 2.5-2.5 1-1.5v15h4V20l1 1.5 2.5 2.5Z",
58
+ video:
59
+ "M36 4H12c-4.5 0-8 3.5-8 8v24c0 4.5 3.5 8 8 8h24c4.5 0 8-3.5 8-8V12c0-4.5-3.5-8-8-8zM12 8h24a4 4 0 014 4v24a4 4 0 01-4 4H12a4 4 0 01-4-4V12a4 4 0 014-4zm8 22l12-6-12-6v12zm-8-20h2v4h-4v-2c0-1.1.9-2 2-2zm6 0h4v4h-4v-4zm8 0h4v4h-4v-4zm8 0h2a2 2 0 012 2v2h-4v-4zM10 34h4v4h-2a2 2 0 01-2-2v-2zm8 0h4v4h-4v-4zm8 0h4v4h-4v-4zm8 0h4v2a2 2 0 01-2 2h-2v-4z",
60
+ warning:
61
+ "M24 4.2c2 0 4.3 1.3 5 2.8l15 28c2.5 5-2 9-5 9H9c-3 0-7.5-4-5-9L19 7c.8-1.5 3-2.8 5-2.8Zm0 4c-.8 0-1.5.6-2 1.6L8 36.1C6.7 38.5 8 40 9.5 40h29c1.5 0 2.8-1.5 1.5-3.8L26 9.8c-.5-1-1.2-1.4-2-1.4ZM26 32v4h-4v-4h4Zm0-16v14h-4V16h4Z",
62
+ };
63
+
64
+ if (!paths[name]) {
65
+ return;
66
+ }
67
+
68
+ const svg = title
69
+ ? `<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="1em" width="1em" viewBox="0 0 48 48" aria-labelledby="${name}-title" role="img">
70
+ <title id="${name}-title">${title}</title>
71
+ <path fill="currentColor" d="${paths[name]}"/>
72
+ </svg>`
73
+ : `<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="1em" width="1em" viewBox="0 0 48 48" focusable="false" aria-hidden="true">
74
+ <path fill="currentColor" d="${paths[name]}"/>
75
+ </svg>`;
76
+
77
+ const icon = svg.replaceAll(/(\s{2,}|\r\n\t|\n|\r\t)/gm, "");
78
+
79
+ return icon;
80
+ };
@@ -0,0 +1,12 @@
1
+ export { attributes } from "./attributes.js";
2
+ export { classes } from "./classes.js";
3
+ export { errorList } from "./error-list.js";
4
+ export { fieldData } from "./field-data.js";
5
+ export { icon } from "./icon.js";
6
+ export { itemId } from "./item-id.js";
7
+ export { summaryRows } from "./summary-rows.js";
8
+ export {
9
+ getBackgroundColor as backgroundColor,
10
+ getThemeColor as themeColor,
11
+ getThemeCustomProperties as themeCustomProperties,
12
+ } from "../utils/theme.js";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Get item `id` for radio/checkbox
3
+ * @param {string} id - Item `id`
4
+ * @param {string} idPrefix - Prefix for each item `id` if no `id` specified
5
+ * @param {object} loop - Nunjucks for loop object
6
+ * @returns {string} Item `id`
7
+ */
8
+ export const itemId = (id, idPrefix, loop) => {
9
+ // If user explicitly sets `id`, use this instead of `idPrefix`
10
+ if (id) {
11
+ return id;
12
+ }
13
+
14
+ // The first `id` should not have a number suffix so
15
+ // easier to link to from the error summary component
16
+ return loop.first ? idPrefix : `${idPrefix}-${loop.index}`;
17
+ };
@@ -0,0 +1,39 @@
1
+ import { markdown } from "../filters/string.js";
2
+
3
+ const _valueText = (value) => {
4
+ if (typeof value === "string") {
5
+ return `<code class="token string">"${value}"</code>`;
6
+ }
7
+
8
+ if (typeof value === "object" && typeof value[0] === "boolean") {
9
+ return `<code class="token boolean">${value}</code>`;
10
+ }
11
+
12
+ try {
13
+ return markdown(`~~~js\n${JSON.stringify(value, undefined, 2)}\n~~~`);
14
+ } catch {
15
+ return `<code class="token class-name">${value.constructor.name}</code>`;
16
+ }
17
+ };
18
+
19
+ /**
20
+ * Transform object to array for consumption by summary component
21
+ * @param {object} object - Object
22
+ * @returns {Array} Rows
23
+ */
24
+ export const summaryRows = (object) => {
25
+ const rows = [];
26
+
27
+ for (const [key, value] of Object.entries(object)) {
28
+ rows.push({
29
+ key: {
30
+ text: key,
31
+ },
32
+ value: {
33
+ text: _valueText(value),
34
+ },
35
+ });
36
+ }
37
+
38
+ return rows;
39
+ };
@@ -0,0 +1,41 @@
1
+ import fs from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import { bundleAsync } from "lightningcss";
6
+
7
+ const require = createRequire(import.meta.url);
8
+
9
+ /**
10
+ * Resolve path to file in `node_modules`
11
+ * @param {string} filePath - File path
12
+ * @returns {string} Resolved file path
13
+ * @example `@import url("~codemirror/lib/codemirror.css");`
14
+ */
15
+ function resolveModuleFilePath(filePath) {
16
+ if (filePath.includes("~")) {
17
+ const moduleFilePath = filePath.split("~")[1];
18
+ return require.resolve(moduleFilePath);
19
+ }
20
+
21
+ return filePath;
22
+ }
23
+
24
+ export const styles = async () => {
25
+ const inputFile = fileURLToPath(
26
+ new URL("../styles/app.css", import.meta.url),
27
+ );
28
+
29
+ let { code } = await bundleAsync({
30
+ filename: inputFile,
31
+ minify: true,
32
+ resolver: {
33
+ read(filePath) {
34
+ filePath = resolveModuleFilePath(filePath);
35
+ return fs.readFileSync(filePath, "utf8");
36
+ },
37
+ },
38
+ });
39
+
40
+ return code.toString();
41
+ };