diff --git a/package-lock.json b/package-lock.json
index d53c983..fa366d0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,9 +14,10 @@
"@koa/router": "^12.0.1",
"@sealcode/add-to-head": "^1.0.0",
"@sealcode/file-manager": "^1.0.2",
- "@sealcode/jdd": "^0.8.4",
- "@sealcode/jdd-editor": "^0.2.9",
- "@sealcode/sealgen": "^0.19.6",
+ "@sealcode/jdd": "^0.8.6",
+ "@sealcode/jdd-editor": "^0.2.18",
+ "@sealcode/monaco-wrapper": "^0.0.4",
+ "@sealcode/sealgen": "^0.19.20",
"@sealcode/show-first-row": "^0.1.0",
"@sealcode/simplemde": "^1.12.1",
"@sealcode/sortable": "^0.0.1",
@@ -25,6 +26,7 @@
"@types/leaflet": "^1.9.8",
"@types/object-hash": "^3.0.6",
"@types/simplemde": "^1.11.11",
+ "a11y.css": "^5.3.0",
"c8": "^10.1.3",
"colord": "^2.9.3",
"dotenv": "^16.4.5",
@@ -32,6 +34,7 @@
"get-port": "^7.0.0",
"glightbox": "^3.3.1",
"glob": "^11.0.3",
+ "highlight.js": "^11.11.1",
"js-convert-case": "^4.2.0",
"koa-mount": "^4.2.0",
"koa-qs": "^3.0.0",
@@ -924,8 +927,9 @@
}
},
"node_modules/@sealcode/file-manager": {
- "version": "1.0.2",
- "license": "ISC",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@sealcode/file-manager/-/file-manager-1.0.3.tgz",
+ "integrity": "sha512-S2E1Yh7an8i9LEnX1FNvGXZwiHeWmg4dwXhMZR62xaRP7JjUve/2630aIYdiL7Ci6WLOsI7XsPhEQDlbQvQTDA==",
"dependencies": {
"@types/mime-types": "^2.1.4",
"@types/uuid": "^9.0.8",
@@ -935,8 +939,9 @@
}
},
"node_modules/@sealcode/jdd": {
- "version": "0.8.4",
- "license": "ISC",
+ "version": "0.8.7",
+ "resolved": "https://registry.npmjs.org/@sealcode/jdd/-/jdd-0.8.7.tgz",
+ "integrity": "sha512-5kFackogIfsulfQlMezex1QZ/If2aT17C+RAtSArJbQIHi1oDqy9l8+yV4PPdgDNxuRK+M/AYI7hgexZpEpSKg==",
"dependencies": {
"@sealcode/file-manager": "^1.0.2",
"@sealcode/ts-predicates": "^0.5.3",
@@ -957,12 +962,12 @@
}
},
"node_modules/@sealcode/jdd-editor": {
- "version": "0.2.9",
- "resolved": "https://registry.npmjs.org/@sealcode/jdd-editor/-/jdd-editor-0.2.9.tgz",
- "integrity": "sha512-5/95i5ZiENX46irBHkiug3brNlrXYhpr+1nAX1OtqWKFQahgeERUExJUfjiTerwWs2N5HzpL/5VuElCR5wx49Q==",
+ "version": "0.2.18",
+ "resolved": "https://registry.npmjs.org/@sealcode/jdd-editor/-/jdd-editor-0.2.18.tgz",
+ "integrity": "sha512-14oT7FDjKWuH5aMm5C2jsdpFAGjz+cQjomj4aLMrMkPUtPSYtKV7CUxseM+OJKynSJl05iPTAtc1FebydqlRHQ==",
"dependencies": {
"@koa/router": "^13.1.0",
- "@sealcode/jdd": "^0.8.3",
+ "@sealcode/jdd": "^0.8.6",
"@sealcode/monaco-wrapper": "^0.0.3",
"@sealcode/sealcodemirror": "^5.70.0-beta5",
"@sealcode/sealgen": "^0.19.6",
@@ -991,13 +996,22 @@
"node": ">= 18"
}
},
- "node_modules/@sealcode/jdd-editor/node_modules/highlight.js": {
- "version": "11.11.1",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=12.0.0"
+ "node_modules/@sealcode/jdd-editor/node_modules/@sealcode/monaco-wrapper": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@sealcode/monaco-wrapper/-/monaco-wrapper-0.0.3.tgz",
+ "integrity": "sha512-EAmcW5lDVeEMpdVfhxJM9psuBFgrD1Wr2JJrBg3ifkut4y5mTHuaHfkut3BrzgifuPeukUJZuyX+sncFs26c7g==",
+ "dependencies": {
+ "@sealcode/add-to-head": "^1.0.0",
+ "monaco-editor": "^0.52.2",
+ "stimulus": "^3.2.2",
+ "throttle-debounce": "^5.0.2"
}
},
+ "node_modules/@sealcode/jdd-editor/node_modules/monaco-editor": {
+ "version": "0.52.2",
+ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
+ "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ=="
+ },
"node_modules/@sealcode/jdd/node_modules/@sealcode/ts-predicates": {
"version": "0.5.3",
"license": "ISC"
@@ -1016,8 +1030,9 @@
}
},
"node_modules/@sealcode/monaco-wrapper": {
- "version": "0.0.3",
- "license": "ISC",
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@sealcode/monaco-wrapper/-/monaco-wrapper-0.0.4.tgz",
+ "integrity": "sha512-oRksJVfCLPVabExFF4hbzUBS/LVCLRfdqsJp4VbTcd0NPa6kmRs/hDxD+XL1kZ0xkt3EeFeH5gHRmRMtPDAmtA==",
"dependencies": {
"@sealcode/add-to-head": "^1.0.0",
"monaco-editor": "^0.52.2",
@@ -1034,8 +1049,9 @@
"license": "MIT"
},
"node_modules/@sealcode/sealgen": {
- "version": "0.19.6",
- "license": "ISC",
+ "version": "0.19.20",
+ "resolved": "https://registry.npmjs.org/@sealcode/sealgen/-/sealgen-0.19.20.tgz",
+ "integrity": "sha512-x7sI3/C7WtaM23v2lF540Dn0FNw1cAvBfi14Mw4cNHjZKQS7JOv2PjODwm+DOMCTsFtUrz/ElNBCvos0H34tjw==",
"dependencies": {
"@koa/router": "^12.0.1",
"@sealcode/file-manager": "^1.0.2",
@@ -1071,7 +1087,7 @@
"peerDependencies": {
"koa": "^2.13.0",
"koa-responsive-image-router": "^0.2.24",
- "sealious": "^0.21.20",
+ "sealious": "^0.21.36",
"stimulus": "^3.2.2"
}
},
@@ -1936,6 +1952,11 @@
"license": "Apache-2.0",
"peer": true
},
+ "node_modules/a11y.css": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/a11y.css/-/a11y.css-5.3.0.tgz",
+ "integrity": "sha512-WBEnwAT3NHyJFN0ejedhc4Q0U4yX2fNWmoee8Ai6TWIICjrocTNugXm8XFuIC8dI8goaIoqxnSd4PlnljJ8dxw=="
+ },
"node_modules/abbrev": {
"version": "2.0.0",
"license": "ISC",
@@ -2995,7 +3016,8 @@
},
"node_modules/color-convert": {
"version": "3.1.3",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz",
+ "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==",
"dependencies": {
"color-name": "^2.0.0"
},
@@ -3009,14 +3031,16 @@
},
"node_modules/color-name": {
"version": "2.1.0",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz",
+ "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==",
"engines": {
"node": ">=12.20"
}
},
"node_modules/color-string": {
"version": "2.1.4",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz",
+ "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==",
"dependencies": {
"color-name": "^2.0.0"
},
@@ -5909,10 +5933,11 @@
}
},
"node_modules/highlight.js": {
- "version": "10.7.3",
- "license": "BSD-3-Clause",
+ "version": "11.11.1",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
+ "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
"engines": {
- "node": "*"
+ "node": ">=12.0.0"
}
},
"node_modules/hookable": {
@@ -10548,11 +10573,12 @@
"peer": true
},
"node_modules/sealious": {
- "version": "0.21.30",
- "license": "BSD-2-Clause",
+ "version": "0.21.37",
+ "resolved": "https://registry.npmjs.org/sealious/-/sealious-0.21.37.tgz",
+ "integrity": "sha512-sSIBtaOsHl9NUxm0jOnSs79QVh4mdongjd+tzOr8e8fqG8GPiSy8/RTMNwaUF7+csVW3OvEaa98Da7YD3LRfoA==",
"dependencies": {
"@koa/router": "^12.0.1",
- "@sealcode/file-manager": "^1.0.1",
+ "@sealcode/file-manager": "^1.0.3",
"@sealcode/ts-predicates": "^0.4.3",
"@types/boom": "^7.3.0",
"@types/clone": "^0.1.30",
@@ -12481,6 +12507,14 @@
"node": ">=6 <7 || >=8"
}
},
+ "node_modules/typedoc/node_modules/highlight.js": {
+ "version": "10.7.3",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
+ "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/typedoc/node_modules/jsonfile": {
"version": "4.0.0",
"license": "MIT",
diff --git a/package.json b/package.json
index 2f3406d..81d4bfd 100644
--- a/package.json
+++ b/package.json
@@ -86,6 +86,10 @@
{
"from": "node_modules/glightbox/dist",
"to": "dist/glightbox"
+ },
+ {
+ "from": "node_modules/a11y.css/css",
+ "to": "dist/a11y.css"
}
]
},
@@ -97,9 +101,10 @@
"@koa/router": "^12.0.1",
"@sealcode/add-to-head": "^1.0.0",
"@sealcode/file-manager": "^1.0.2",
- "@sealcode/jdd": "^0.8.4",
- "@sealcode/jdd-editor": "^0.2.9",
- "@sealcode/sealgen": "^0.19.6",
+ "@sealcode/jdd": "^0.8.6",
+ "@sealcode/jdd-editor": "^0.2.18",
+ "@sealcode/monaco-wrapper": "^0.0.4",
+ "@sealcode/sealgen": "^0.19.20",
"@sealcode/show-first-row": "^0.1.0",
"@sealcode/simplemde": "^1.12.1",
"@sealcode/sortable": "^0.0.1",
@@ -108,6 +113,7 @@
"@types/leaflet": "^1.9.8",
"@types/object-hash": "^3.0.6",
"@types/simplemde": "^1.11.11",
+ "a11y.css": "^5.3.0",
"c8": "^10.1.3",
"colord": "^2.9.3",
"dotenv": "^16.4.5",
@@ -115,6 +121,7 @@
"get-port": "^7.0.0",
"glightbox": "^3.3.1",
"glob": "^11.0.3",
+ "highlight.js": "^11.11.1",
"js-convert-case": "^4.2.0",
"koa-mount": "^4.2.0",
"koa-qs": "^3.0.0",
diff --git a/src/back/a11y_overrides.css b/src/back/a11y_overrides.css
new file mode 100644
index 0000000..ae442c7
--- /dev/null
+++ b/src/back/a11y_overrides.css
@@ -0,0 +1,11 @@
+/* we properly embed charset metadata, but Turbo changes the order of elements in
and this rule gives a false positive */
+head :first-child:not([charset]) {
+ &,
+ &::after {
+ display: none !important;
+ }
+}
+
+head :first-child:not([charset]) ~ link:last-of-type::before {
+ display: none !important;
+}
diff --git a/src/back/config.ts b/src/back/config.ts
index 8936892..b46967d 100644
--- a/src/back/config.ts
+++ b/src/back/config.ts
@@ -40,7 +40,12 @@ export const IMAGE_CACHE_FS_DIR =
process.env.IMAGE_CACHE_FS_DIR || locreq.resolve("cache/images");
export const SMARTCROP_CACHE_FS_DIR =
process.env.IMAGE_CACHE_FS_DIR || locreq.resolve("cache/smartcrop");
+export const IMAGE_CACHE_SIZE = parseInt(process.env.IMAGE_CACHE_SIZE || "1000");
+export const IMAGE_SMARTCROP_CACHE_SIZE = parseInt(process.env.IMAGE_CACHE_SIZE || "500");
+export const IMAGE_THUMBNAIL_CACHE_SIZE = parseInt(process.env.IMAGE_CACHE_SIZE || "500");
+
export const UPLOADS_FS_DIR =
process.env.UPLOADS_FS_DIR || locreq.resolve("uploaded_files");
export const MEILISEARCH_MASTER_KEY = process.env.MEILISEARCH_MASTER_KEY || "qwerty";
export const MEILISEARCH_HOST = process.env.MEILISEARCH_HOST || "http://localhost:1098";
+export const SCAN_A11Y = false;
diff --git a/src/back/defaultHead.tsx b/src/back/defaultHead.tsx
index b2a1221..e9fb9f6 100644
--- a/src/back/defaultHead.tsx
+++ b/src/back/defaultHead.tsx
@@ -3,6 +3,7 @@ import type { Readable } from "stream";
import type { Context } from "koa";
import type { HTMLOptions } from "@sealcode/sealgen";
import { htmlEscape } from "escape-goat";
+import { SCAN_A11Y } from "./config.js";
export const start_timestamp = Date.now();
@@ -24,13 +25,16 @@ export function defaultHead({
description: string;
}): JSX.Element | Readable {
const origin = ctx.URL.origin;
- return tempstream /* HTML */ `${title}
+ return tempstream /* HTML */ ` ${title}
+ ${SCAN_A11Y ? `` : ""}
${ctx.$app.getFeedHTMLMetatags(ctx)}
- ${metaImage ? `` : ""}
+ ${metaImage
+ ? ``
+ : ""}
${[
"default",
"page",
diff --git a/src/back/html.tsx b/src/back/html.tsx
index ef025ca..9b13cf3 100644
--- a/src/back/html.tsx
+++ b/src/back/html.tsx
@@ -1,10 +1,10 @@
import type { HTMLArgs, HTMLOptions } from "@sealcode/sealgen";
-import { tempstreamAsync } from "tempstream";
-import type { Readable } from "stream";
import { toKebabCase } from "js-convert-case";
+import type { Readable } from "stream";
+import { tempstreamAsync } from "tempstream";
import { DEFAULT_HTML_LANG } from "./config.js";
-import { default_navbar } from "./routes/common/navbar.js";
import { defaultHead } from "./defaultHead.js";
+import { default_navbar } from "./routes/common/navbar.js";
const default_html_options: Partial = {
showFooter: true,
@@ -40,6 +40,8 @@ export default function html({
controllers.push("refresh-on-ts-changes");
}
+ const classes = (htmlOptions?.bodyClasses || []).join(" ");
+
return tempstreamAsync /* HTML */ `
${!hideNavigation ? (htmlOptions?.navbar || default_navbar)(ctx) : ""}
${body}
diff --git a/src/back/image-router.ts b/src/back/image-router.ts
index 0e5ff68..2946449 100644
--- a/src/back/image-router.ts
+++ b/src/back/image-router.ts
@@ -1,14 +1,23 @@
import { KoaResponsiveImageRouter } from "koa-responsive-image-router";
-import { IMAGE_CACHE_FS_DIR, SMARTCROP_CACHE_FS_DIR } from "./config.js";
+import {
+ IMAGE_CACHE_FS_DIR,
+ SMARTCROP_CACHE_FS_DIR,
+ IMAGE_CACHE_SIZE,
+ IMAGE_SMARTCROP_CACHE_SIZE,
+ IMAGE_THUMBNAIL_CACHE_SIZE,
+} from "./config.js";
export const RESPONSIVE_IMAGES_URL_PATH = "/images";
export const imageRouter = new KoaResponsiveImageRouter({
staticPath: RESPONSIVE_IMAGES_URL_PATH,
thumbnailSize: 20,
- cacheManagerResolutionThreshold: 50,
+ cacheManagerResolutionThreshold: 21,
imageStoragePath: IMAGE_CACHE_FS_DIR,
smartCropStoragePath: SMARTCROP_CACHE_FS_DIR,
formatsForLossy: ["webp"],
formatsForLossless: ["webp"],
+ diskImageCacheSize: IMAGE_CACHE_SIZE,
+ smartCropCacheSize: IMAGE_SMARTCROP_CACHE_SIZE,
+ thumbnailMaxCacheSize: IMAGE_THUMBNAIL_CACHE_SIZE,
});
diff --git a/src/back/jdd-components/code-snippet/code-snippet.css b/src/back/jdd-components/code-snippet/code-snippet.css
new file mode 100644
index 0000000..e1e0628
--- /dev/null
+++ b/src/back/jdd-components/code-snippet/code-snippet.css
@@ -0,0 +1,22 @@
+.code-snippet {
+ display: flex;
+ flex-direction: column;
+ padding: 0 16px 16px 16px;
+}
+
+.code-snippet pre {
+ margin: 0;
+}
+
+.code-snippet__language-error {
+ color: red;
+ font-size: 16px;
+}
+
+.code-snippet__language {
+ display: flex;
+ justify-content: flex-start;
+ margin: 0;
+ margin-top: 20px;
+ gap: 8px;
+}
\ No newline at end of file
diff --git a/src/back/jdd-components/code-snippet/code-snippet.jdd.tsx b/src/back/jdd-components/code-snippet/code-snippet.jdd.tsx
new file mode 100644
index 0000000..734226c
--- /dev/null
+++ b/src/back/jdd-components/code-snippet/code-snippet.jdd.tsx
@@ -0,0 +1,54 @@
+import { TempstreamJSX } from "tempstream";
+import type { ComponentToHTMLArgs } from "@sealcode/jdd";
+import { Component, ComponentArguments } from "@sealcode/jdd";
+import hljs from "highlight.js";
+
+const component_arguments = {
+ codeWithLanguage: new ComponentArguments.CodeWithCustomLanguage(),
+} as const;
+
+export class CodeSnippet extends Component {
+ getArguments() {
+ return component_arguments;
+ }
+
+ async toHTML({
+ args: { codeWithLanguage },
+ classes,
+ }: ComponentToHTMLArgs): Promise {
+ let highlightedCode: string;
+ let languageNotFoundMessage: string = "";
+
+ const language = codeWithLanguage.language?.toLowerCase() || "plaintext";
+
+ if (hljs.getLanguage(language)) {
+ highlightedCode = hljs.highlight(codeWithLanguage.code, {
+ language,
+ }).value;
+ } else {
+ highlightedCode = hljs.highlight(codeWithLanguage.code, {
+ language: "plaintext",
+ }).value;
+ languageNotFoundMessage = `Language "${language}" not found`;
+ }
+
+ return (
+
+
CodeSnippet
+ {languageNotFoundMessage ? (
+
{languageNotFoundMessage}
+ ) : (
+ <>
+
+ {highlightedCode}
+
+
+ {codeWithLanguage.language}
+
+
+ >
+ )}
+
+ );
+ }
+}
diff --git a/src/back/jdd-components/faq-component/faq-component.jdd.tsx b/src/back/jdd-components/faq-component/faq-component.jdd.tsx
index 1265ec3..c852d83 100644
--- a/src/back/jdd-components/faq-component/faq-component.jdd.tsx
+++ b/src/back/jdd-components/faq-component/faq-component.jdd.tsx
@@ -55,7 +55,7 @@ export class FaqComponent extends Component {
>
{element.question}
-
{render_markdown(language, element.answer)}
+ {render_markdown(language, element.answer)}
))}
diff --git a/src/back/routes/common/breadcrumbs.css b/src/back/routes/common/breadcrumbs.css
new file mode 100644
index 0000000..e86369e
--- /dev/null
+++ b/src/back/routes/common/breadcrumbs.css
@@ -0,0 +1,58 @@
+.breadcrumbs {
+ margin-top: 16px;
+ margin-left: 16px;
+ --bg: var(--color-brand-accent);
+ display: flex;
+ --height: 2rem;
+ --padding: 0px 16px;
+ flex-flow: row wrap;
+ align-items: center;
+ row-gap: 8px;
+
+ span {
+ padding: var(--padding);
+ }
+
+ a,
+ & > span {
+ display: inline-block;
+ line-height: var(--height);
+ font-size: 1rem;
+ }
+
+ a {
+ text-decoration: none;
+ display: flex;
+ flex-flow: row nowrap;
+ color: var(--color-brand-text-on-accent);
+
+ span {
+ background-color: var(--bg);
+ }
+
+ .arrow-right,
+ .arrow-left {
+ width: 0;
+ height: 0;
+ }
+
+ .arrow-right {
+ border-top: calc(var(--height) / 2) solid transparent;
+ border-bottom: calc(var(--height) / 2) solid transparent;
+
+ border-left: calc(var(--height) / 2) solid var(--bg);
+ }
+
+ .arrow-left {
+ width: 0;
+ height: 0;
+ border-top: calc(var(--height) / 2) solid var(--bg);
+ border-bottom: calc(var(--height) / 2) solid var(--bg);
+ border-left: calc(var(--height) / 2) solid transparent;
+ }
+
+ &:hover {
+ filter: brightness(1.1);
+ }
+ }
+}
diff --git a/src/back/routes/common/breadcrumbs.tsx b/src/back/routes/common/breadcrumbs.tsx
new file mode 100644
index 0000000..f0a9c84
--- /dev/null
+++ b/src/back/routes/common/breadcrumbs.tsx
@@ -0,0 +1,71 @@
+import type { Context } from "koa";
+import { TempstreamJSX } from "tempstream";
+import { get_breadcrumbs_from_path } from "../url-tree.js";
+
+const arrow_width = 8;
+const arrow_height = 28;
+
+function arrow_tail() {
+ return (
+
+ );
+}
+
+function arrow_head() {
+ return (
+
+ );
+}
+
+export async function breadcrumbs(ctx: Context) {
+ const breadcrumbs = await Promise.all(
+ get_breadcrumbs_from_path(ctx.path).map(async (e, i, all) => {
+ if (!e.actionName) {
+ return "";
+ }
+
+ const breadcrumbLabel = e.breadcrumbLabel?.({ ...ctx });
+ const breadcrumbLabelResult = await breadcrumbLabel;
+
+ if (i == all.length - 1) {
+ return {breadcrumbLabelResult || e.actionName};
+ }
+ return (
+ <>
+
+ {i !== 0 ? arrow_tail() : ""}
+ {breadcrumbLabelResult || e.actionName}
+ {arrow_head()}
+
+ >
+ );
+ })
+ );
+ return {breadcrumbs.filter((e) => e !== "").join("")}
;
+}
diff --git a/src/back/routes/common/navbar.ts b/src/back/routes/common/navbar.ts
index 48aee95..2dc20e4 100644
--- a/src/back/routes/common/navbar.ts
+++ b/src/back/routes/common/navbar.ts
@@ -1,9 +1,10 @@
-import type { BaseContext } from "koa";
+import type { Context } from "koa";
import { button } from "src/back/elements/button.js";
import { tempstream } from "tempstream";
import type { FlatTemplatable } from "tempstream";
+import { breadcrumbs } from "./breadcrumbs.js";
-export async function default_navbar(ctx: BaseContext): Promise {
+export async function default_navbar(ctx: Context): Promise {
const { items: navbar_items } = await ctx.$app.collections["navbar-links"]
.list(ctx.$context)
.fetch();
@@ -25,18 +26,20 @@ export async function default_navbar(ctx: BaseContext): Promise
)
.join("\n");
- return /* HTML */ ``;
+ return tempstream /* HTML */ `
+ ${breadcrumbs(ctx)} `;
}
diff --git a/src/back/routes/hello-page.page.tsx b/src/back/routes/hello-page.page.tsx
index 34bf7f7..244a612 100644
--- a/src/back/routes/hello-page.page.tsx
+++ b/src/back/routes/hello-page.page.tsx
@@ -2,9 +2,12 @@ import type { Context } from "koa";
import { TempstreamJSX } from "tempstream";
import { Page } from "@sealcode/sealgen";
+import type { BreadcrumbLabel } from "@sealcode/sealgen";
+
import html from "src/back/html.js";
export const actionName = "HelloPage";
+export const breadcrumbLabel: BreadcrumbLabel = "Witaj";
export default new (class HelloPagePage extends Page {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
diff --git a/src/back/routes/login.form.ts b/src/back/routes/login.form.ts
index 751d775..48329f7 100644
--- a/src/back/routes/login.form.ts
+++ b/src/back/routes/login.form.ts
@@ -67,7 +67,7 @@ export default new (class LoginForm extends Form {
async render(ctx: Context, data: FormData, show_field_errors: boolean) {
return html({
ctx,
- title: "Form",
+ title: "Sign in",
description: "",
css_clumps: ["admin-forms"],
body: super.render(ctx, data, show_field_errors) as Promise,
diff --git a/src/back/routes/url-tree.ts b/src/back/routes/url-tree.ts
new file mode 100644
index 0000000..9b5f92c
--- /dev/null
+++ b/src/back/routes/url-tree.ts
@@ -0,0 +1,49 @@
+import * as path from "path";
+import type { Context } from "koa";
+import { url_tree } from "./routes.js";
+import type { URLTree } from "./routes.js";
+import type { BreadcrumbLabel } from "@sealcode/sealgen";
+
+export function get_breadcrumbs_from_path(url: string) {
+ let position: URLTree = url_tree;
+ const elements = url.split("/").filter((e) => e != "");
+ const breadcrumbs: {
+ actionName?: string;
+ url?: string;
+ breadcrumbLabel?: (ctx: Context) => Promise;
+ }[] = [{ actionName: "Home", url: "/" }];
+ let path_so_far = "";
+ for (const element of elements) {
+ if (position.children[element]) {
+ position = position.children[element];
+ } else if (Object.keys(position.children).find((e) => e.startsWith(":"))) {
+ const key = Object.keys(position.children).find((e) => e.startsWith(":"));
+ position = position.children[key!]!;
+ } else {
+ break;
+ }
+ path_so_far += "/" + element;
+ const actionName = position.actionName;
+ const modulePath = position.module_path;
+
+ breadcrumbs.push({
+ actionName: actionName,
+ url: path_so_far,
+ breadcrumbLabel: async (ctx: Context) => {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/consistent-type-assertions
+ const module = (await import(path.resolve(modulePath || "") || "")) as {
+ breadcrumbLabel: BreadcrumbLabel;
+ };
+
+ if (!module.breadcrumbLabel) return actionName || "";
+
+ if (typeof module.breadcrumbLabel === "function") {
+ return module.breadcrumbLabel(ctx);
+ } else {
+ return module.breadcrumbLabel;
+ }
+ },
+ });
+ }
+ return breadcrumbs;
+}
diff --git a/src/front/controllers.ts b/src/front/controllers.ts
index 7c76f47..99310ed 100644
--- a/src/front/controllers.ts
+++ b/src/front/controllers.ts
@@ -22,6 +22,9 @@ application.register("glightbox", Glightbox);
import { default as AutogrowTextarea } from "./../../node_modules/@sealcode/jdd-editor/src/controllers/autogrow-textarea.stimulus.js";
application.register("autogrow-textarea", AutogrowTextarea);
+import { default as CodeSnippet } from "./../../node_modules/@sealcode/jdd-editor/src/controllers/code-snippet.stimulus.js";
+application.register("code-snippet", CodeSnippet);
+
import { default as ComponentDebugger } from "./../../node_modules/@sealcode/jdd-editor/src/controllers/component-debugger.stimulus.js";
application.register("component-debugger", ComponentDebugger);
@@ -37,9 +40,6 @@ application.register("jdd-table-paste", JddTablePaste);
import { default as JsonEditor } from "./../../node_modules/@sealcode/jdd-editor/src/controllers/json-editor.stimulus.js";
application.register("json-editor", JsonEditor);
-import { default as MarkdownTextarea } from "./../../node_modules/@sealcode/jdd-editor/src/controllers/markdown-textarea.stimulus.js";
-application.register("markdown-textarea", MarkdownTextarea);
-
import { default as RefreshOnTsChanges } from "./../../node_modules/@sealcode/jdd-editor/src/controllers/refresh-on-ts-changes.stimulus.js";
application.register("refresh-on-ts-changes", RefreshOnTsChanges);
@@ -55,8 +55,11 @@ application.register("toast", Toast);
import { default as Monaco } from "./../../node_modules/@sealcode/monaco-wrapper/src/controllers/monaco.stimulus.js";
application.register("monaco", Monaco);
-import { default as TableAddButton } from "./../../node_modules/@sealcode/sealgen/src/controllers/table-add-button.stimulus.js";
-application.register("table-add-button", TableAddButton);
+import { default as PasswordGenerateButton } from "./../../node_modules/@sealcode/sealgen/src/controllers/password-generate-button.stimulus.js";
+application.register("password-generate-button", PasswordGenerateButton);
+
+import { default as SealgenTable } from "./../../node_modules/@sealcode/sealgen/src/controllers/sealgen-table.stimulus.js";
+application.register("sealgen-table", SealgenTable);
import { default as Sortable } from "./../../node_modules/@sealcode/sortable/src/controllers/sortable.stimulus.js";
application.register("sortable", Sortable);
diff --git a/src/main.css b/src/main.css
index fc04586..93ae47f 100644
--- a/src/main.css
+++ b/src/main.css
@@ -1,3 +1,7 @@
+* {
+ box-sizing: border-box;
+}
+
html {
background: var(--color-brand-canvas);
font-family: var(--font-sans-serif);
@@ -103,16 +107,11 @@ h4 {
display: grid !important;
--min-content-width: 320px;
--main-column-max-width: 1224px;
- grid-template-columns: [screen-left-edge] minmax(
- 16px,
- 1fr
- ) [content-left-edge left-column-start] minmax(
- 120px,
- 260px
- ) [left-column-end] 24px [right-column-start] minmax(
- 120px,
- 260px
- ) [content-right-edge right-column-end] minmax(16px, 1fr) [screen-right-edge];
+ grid-template-columns:
+ [screen-left-edge] minmax(16px, 1fr)
+ [content-left-edge left-column-start] minmax(120px, 260px)
+ [left-column-end] 24px [right-column-start] minmax(120px, 260px)
+ [content-right-edge right-column-end] minmax(16px, 1fr) [screen-right-edge];
@container (max-width: 550px) {
grid-template-columns:
@@ -153,3 +152,7 @@ h4 {
}
}
}
+
+.sealious-list-wrapper {
+ padding: 16px;
+}