Merge remote-tracking branch 'playground/master'
This commit is contained in:
commit
cdf0c45e8d
94
package-lock.json
generated
94
package-lock.json
generated
@ -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",
|
||||
|
||||
13
package.json
13
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",
|
||||
|
||||
11
src/back/a11y_overrides.css
Normal file
11
src/back/a11y_overrides.css
Normal file
@ -0,0 +1,11 @@
|
||||
/* we properly embed charset metadata, but Turbo changes the order of elements in <head> 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;
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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>${title}</title>
|
||||
return tempstream /* HTML */ `<meta charset="utf-8" /> <title>${title}</title>
|
||||
${SCAN_A11Y ? `<link rel="stylesheet" href="/dist/a11y.css/a11y-en.css">` : ""}
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="${htmlEscape(description)}" />
|
||||
${ctx.$app.getFeedHTMLMetatags(ctx)}
|
||||
<script defer src="/dist/bundle.js?v=${start_timestamp}"></script>
|
||||
${metaImage ? `<meta property="og:image" content="${metaImage}" />` : ""}
|
||||
${metaImage
|
||||
? `<meta property="og:image" content="${metaImage.startsWith("/") ? origin + metaImage : metaImage}" />`
|
||||
: ""}
|
||||
${[
|
||||
"default",
|
||||
"page",
|
||||
|
||||
@ -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<HTMLOptions> = {
|
||||
showFooter: true,
|
||||
@ -40,6 +40,8 @@ export default function html({
|
||||
controllers.push("refresh-on-ts-changes");
|
||||
}
|
||||
|
||||
const classes = (htmlOptions?.bodyClasses || []).join(" ");
|
||||
|
||||
return tempstreamAsync /* HTML */ ` <!DOCTYPE html>
|
||||
<html
|
||||
lang="${htmlOptions.language || DEFAULT_HTML_LANG}"
|
||||
@ -58,7 +60,7 @@ export default function html({
|
||||
</head>
|
||||
<body
|
||||
data-controller="${controllers.join(" ")}"
|
||||
class="${(htmlOptions?.bodyClasses || []).join(" ")}"
|
||||
${classes ? `class="${classes}"` : ""}
|
||||
>
|
||||
${!hideNavigation ? (htmlOptions?.navbar || default_navbar)(ctx) : ""}
|
||||
${body}
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
22
src/back/jdd-components/code-snippet/code-snippet.css
Normal file
22
src/back/jdd-components/code-snippet/code-snippet.css
Normal file
@ -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;
|
||||
}
|
||||
54
src/back/jdd-components/code-snippet/code-snippet.jdd.tsx
Normal file
54
src/back/jdd-components/code-snippet/code-snippet.jdd.tsx
Normal file
@ -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<typeof component_arguments> {
|
||||
getArguments() {
|
||||
return component_arguments;
|
||||
}
|
||||
|
||||
async toHTML({
|
||||
args: { codeWithLanguage },
|
||||
classes,
|
||||
}: ComponentToHTMLArgs<typeof component_arguments>): Promise<string> {
|
||||
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 (
|
||||
<div class={["code-snippet", ...classes]} data-controller="code-snippet">
|
||||
<h2>CodeSnippet</h2>
|
||||
{languageNotFoundMessage ? (
|
||||
<p class="code-snippet__language-error">{languageNotFoundMessage}</p>
|
||||
) : (
|
||||
<>
|
||||
<pre>
|
||||
<code data-code-snippet-target="code">{highlightedCode}</code>
|
||||
</pre>
|
||||
<div class="code-snippet__language">
|
||||
<span>{codeWithLanguage.language}</span>
|
||||
<button data-action="code-snippet#copyCode">Copy</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -55,7 +55,7 @@ export class FaqComponent extends Component<typeof component_arguments> {
|
||||
>
|
||||
<summary class="question">{element.question}</summary>
|
||||
<div class="answer">
|
||||
<p>{render_markdown(language, element.answer)}</p>
|
||||
{render_markdown(language, element.answer)}
|
||||
</div>
|
||||
</details>
|
||||
))}
|
||||
|
||||
58
src/back/routes/common/breadcrumbs.css
Normal file
58
src/back/routes/common/breadcrumbs.css
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
71
src/back/routes/common/breadcrumbs.tsx
Normal file
71
src/back/routes/common/breadcrumbs.tsx
Normal file
@ -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 (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={`${arrow_width}px`}
|
||||
height={`${arrow_height}px`}
|
||||
viewBox="0 0 ${arrow_width} ${arrow_height}"
|
||||
style="margin-right: -1px"
|
||||
>
|
||||
{
|
||||
/* HTML */ `<path
|
||||
d="M 0 0 ${arrow_width} 0l0 ${arrow_height} -${arrow_width} 0L${arrow_width} ${arrow_height /
|
||||
2}Z"
|
||||
style="fill:var(--bg);"
|
||||
/>`
|
||||
}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function arrow_head() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={`${arrow_width}px`}
|
||||
height={`${arrow_height}px`}
|
||||
viewBox="0 0 ${arrow_width} ${arrow_height}"
|
||||
>
|
||||
{
|
||||
/* HTML */ `<path
|
||||
d="m 0 0 ${arrow_width} ${arrow_height / 2}L0 ${arrow_height}Z"
|
||||
style="fill:var(--bg);"
|
||||
/>`
|
||||
}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
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 <span>{breadcrumbLabelResult || e.actionName}</span>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<a href={e.url}>
|
||||
{i !== 0 ? arrow_tail() : ""}
|
||||
<span>{breadcrumbLabelResult || e.actionName}</span>
|
||||
{arrow_head()}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
})
|
||||
);
|
||||
return <div class="breadcrumbs">{breadcrumbs.filter((e) => e !== "").join("")}</div>;
|
||||
}
|
||||
@ -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<FlatTemplatable> {
|
||||
export async function default_navbar(ctx: Context): Promise<FlatTemplatable> {
|
||||
const { items: navbar_items } = await ctx.$app.collections["navbar-links"]
|
||||
.list(ctx.$context)
|
||||
.fetch();
|
||||
@ -25,7 +26,7 @@ export async function default_navbar(ctx: BaseContext): Promise<FlatTemplatable>
|
||||
)
|
||||
.join("\n");
|
||||
|
||||
return /* HTML */ `<nav>
|
||||
return tempstream /* HTML */ `<nav>
|
||||
<a href="/" class="nav-logo">
|
||||
<img
|
||||
src="/assets/logo"
|
||||
@ -35,8 +36,10 @@ export async function default_navbar(ctx: BaseContext): Promise<FlatTemplatable>
|
||||
style="width:220px; height: auto"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<ul>
|
||||
${linksHTML}
|
||||
</ul>
|
||||
</nav>`;
|
||||
</nav>
|
||||
${breadcrumbs(ctx)} `;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -67,7 +67,7 @@ export default new (class LoginForm extends Form<typeof fields, void> {
|
||||
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<string>,
|
||||
|
||||
49
src/back/routes/url-tree.ts
Normal file
49
src/back/routes/url-tree.ts
Normal file
@ -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<string>;
|
||||
}[] = [{ 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;
|
||||
}
|
||||
@ -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);
|
||||
|
||||
23
src/main.css
23
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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user