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 ( + + { + /* HTML */ `` + } + + ); +} + +function arrow_head() { + return ( + + { + /* HTML */ `` + } + + ); +} + +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 ; +} 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; +}