Update JDD and adjust the components API

This commit is contained in:
Kuba Orlik 2024-07-10 16:08:55 +02:00
parent 19d04ff48b
commit 106fcc86c8
33 changed files with 700 additions and 222 deletions

1
.base_url Normal file
View File

@ -0,0 +1 @@
https://dep.sealco.de

74
package-lock.json generated
View File

@ -13,11 +13,12 @@
"@hotwired/turbo": "^8.0.2", "@hotwired/turbo": "^8.0.2",
"@koa/router": "^12.0.1", "@koa/router": "^12.0.1",
"@sealcode/file-manager": "^1.0.2", "@sealcode/file-manager": "^1.0.2",
"@sealcode/jdd": "^0.4.10", "@sealcode/jdd": "^0.5.0",
"@sealcode/sealgen": "^0.15.45", "@sealcode/sealgen": "^0.15.45",
"@sealcode/ts-predicates": "^0.6.2", "@sealcode/ts-predicates": "^0.6.2",
"@types/kill-port": "^2.0.0", "@types/kill-port": "^2.0.0",
"@types/leaflet": "^1.9.8", "@types/leaflet": "^1.9.8",
"@types/turndown": "^5.0.4",
"escape-goat": "^4.0.0", "escape-goat": "^4.0.0",
"get-port": "^7.0.0", "get-port": "^7.0.0",
"js-convert-case": "^4.2.0", "js-convert-case": "^4.2.0",
@ -29,7 +30,9 @@
"qs": "^6.12.0", "qs": "^6.12.0",
"sealious": "^0.19.9", "sealious": "^0.19.9",
"stimulus": "^3.2.2", "stimulus": "^3.2.2",
"tempstream": "^0.3.16" "tempstream": "^0.3.16",
"throttle-debounce": "^5.0.2",
"turndown": "^7.2.0"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.44.1", "@playwright/test": "^1.44.1",
@ -38,6 +41,7 @@
"@types/node": "^20.8.4", "@types/node": "^20.8.4",
"@types/object-path": "^0.11.4", "@types/object-path": "^0.11.4",
"@types/tedious": "^4.0.7", "@types/tedious": "^4.0.7",
"@types/throttle-debounce": "^5.0.2",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "7.4", "@typescript-eslint/eslint-plugin": "7.4",
"@typescript-eslint/parser": "7.4", "@typescript-eslint/parser": "7.4",
@ -731,6 +735,11 @@
"node": ">= 12" "node": ">= 12"
} }
}, },
"node_modules/@mixmark-io/domino": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz",
"integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="
},
"node_modules/@mongodb-js/saslprep": { "node_modules/@mongodb-js/saslprep": {
"version": "1.1.5", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz",
@ -824,19 +833,24 @@
} }
}, },
"node_modules/@sealcode/jdd": { "node_modules/@sealcode/jdd": {
"version": "0.4.10", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/@sealcode/jdd/-/jdd-0.4.10.tgz", "resolved": "https://registry.npmjs.org/@sealcode/jdd/-/jdd-0.5.0.tgz",
"integrity": "sha512-WAZcE0hMDzs29nhrlhH5k5OQrIPV8zSg44pA5FA1uONpBjg4JSb8Y04gzadP5rTQflV68kOAEjmqJs4MFT4JvA==", "integrity": "sha512-5HkFzV7pHfmLg2Ta9+TocueLJUpQrodJ+n1ymY2RkiAzEuVSCnZr5ZAXpreDVbcz2PxDUq94ygVpOPMtS7pkGQ==",
"dependencies": { "dependencies": {
"@sealcode/file-manager": "^1.0.2", "@sealcode/file-manager": "^1.0.2",
"@sealcode/ts-predicates": "^0.5.3", "@sealcode/ts-predicates": "^0.5.3",
"escape-goat": "^4.0.0", "escape-goat": "^4.0.0",
"koa-responsive-image-router": "^0.2.19", "hyphenopoly": "^5.3.0",
"koa-responsive-image-router": "^0.2.29",
"locreq": "^3.0.0", "locreq": "^3.0.0",
"marked": "^12.0.0", "marked": "^12.0.0",
"mri": "^1.2.0", "mri": "^1.2.0",
"tempstream": "^0.3.4", "prettier": "^2.7.1",
"tempstream": "^0.3.18",
"uuid": "^9.0.1" "uuid": "^9.0.1"
},
"peerDependencies": {
"sealious": "^0.19.18"
} }
}, },
"node_modules/@sealcode/jdd/node_modules/@sealcode/ts-predicates": { "node_modules/@sealcode/jdd/node_modules/@sealcode/ts-predicates": {
@ -1347,6 +1361,18 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/throttle-debounce": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
"integrity": "sha512-pDzSNulqooSKvSNcksnV72nk8p7gRqN8As71Sp28nov1IgmPKWbOEIwAWvBME5pPTtaXJAvG3O4oc76HlQ4kqQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/turndown": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.4.tgz",
"integrity": "sha512-28GI33lCCkU4SGH1GvjDhFgOVr+Tym4PXGBIU1buJUa6xQolniPArtUT+kv42RR2N9MsMLInkr904Aq+ESHBJg=="
},
"node_modules/@types/uuid": { "node_modules/@types/uuid": {
"version": "9.0.8", "version": "9.0.8",
"license": "MIT" "license": "MIT"
@ -4742,6 +4768,14 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/hyphenopoly": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/hyphenopoly/-/hyphenopoly-5.3.0.tgz",
"integrity": "sha512-9GajH50TuO+c25VzYUq2luYpyOybpVcDQ4B7fNWy+n3yQ3dSLRFcEC9oJqG96C/rE5Z3zVP8L/K/3ilzBa49PA==",
"engines": {
"node": ">=16"
}
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -8736,8 +8770,9 @@
} }
}, },
"node_modules/sealious": { "node_modules/sealious": {
"version": "0.19.9", "version": "0.19.21",
"license": "BSD-2-Clause", "resolved": "https://registry.npmjs.org/sealious/-/sealious-0.19.21.tgz",
"integrity": "sha512-2LVVD30q9X5MhgmpKnGMQ4351I0awr243DzSFy6sk3ZCsfPo63TZ/5vZbBwhK84Lf+BPRbP9pCkz+m2BdG8AHQ==",
"dependencies": { "dependencies": {
"@koa/router": "^12.0.1", "@koa/router": "^12.0.1",
"@sealcode/file-manager": "^1.0.1", "@sealcode/file-manager": "^1.0.1",
@ -9504,8 +9539,9 @@
} }
}, },
"node_modules/tempstream": { "node_modules/tempstream": {
"version": "0.3.16", "version": "0.3.18",
"license": "ISC", "resolved": "https://registry.npmjs.org/tempstream/-/tempstream-0.3.18.tgz",
"integrity": "sha512-0V/efjocOnjQtBFC6tR8twlI+ygRNgpisOLneVd+Uma9Oic6fo67OqCwEdrIN2DHs3H72St4ACPqHmnlI1YRag==",
"dependencies": { "dependencies": {
"classnames": "^2.5.1" "classnames": "^2.5.1"
} }
@ -9583,6 +9619,14 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true "dev": true
}, },
"node_modules/throttle-debounce": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
"integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==",
"engines": {
"node": ">=12.22"
}
},
"node_modules/tiny-glob": { "node_modules/tiny-glob": {
"version": "0.2.9", "version": "0.2.9",
"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
@ -9864,6 +9908,14 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/turndown": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz",
"integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==",
"dependencies": {
"@mixmark-io/domino": "^2.2.0"
}
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View File

@ -44,11 +44,12 @@
"@hotwired/turbo": "^8.0.2", "@hotwired/turbo": "^8.0.2",
"@koa/router": "^12.0.1", "@koa/router": "^12.0.1",
"@sealcode/file-manager": "^1.0.2", "@sealcode/file-manager": "^1.0.2",
"@sealcode/jdd": "^0.4.10", "@sealcode/jdd": "^0.5.0",
"@sealcode/sealgen": "^0.15.45", "@sealcode/sealgen": "^0.15.45",
"@sealcode/ts-predicates": "^0.6.2", "@sealcode/ts-predicates": "^0.6.2",
"@types/kill-port": "^2.0.0", "@types/kill-port": "^2.0.0",
"@types/leaflet": "^1.9.8", "@types/leaflet": "^1.9.8",
"@types/turndown": "^5.0.4",
"escape-goat": "^4.0.0", "escape-goat": "^4.0.0",
"get-port": "^7.0.0", "get-port": "^7.0.0",
"js-convert-case": "^4.2.0", "js-convert-case": "^4.2.0",
@ -60,7 +61,9 @@
"qs": "^6.12.0", "qs": "^6.12.0",
"sealious": "^0.19.9", "sealious": "^0.19.9",
"stimulus": "^3.2.2", "stimulus": "^3.2.2",
"tempstream": "^0.3.16" "tempstream": "^0.3.16",
"throttle-debounce": "^5.0.2",
"turndown": "^7.2.0"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.44.1", "@playwright/test": "^1.44.1",
@ -69,6 +72,7 @@
"@types/node": "^20.8.4", "@types/node": "^20.8.4",
"@types/object-path": "^0.11.4", "@types/object-path": "^0.11.4",
"@types/tedious": "^4.0.7", "@types/tedious": "^4.0.7",
"@types/throttle-debounce": "^5.0.2",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "7.4", "@typescript-eslint/eslint-plugin": "7.4",
"@typescript-eslint/parser": "7.4", "@typescript-eslint/parser": "7.4",

1
secrets.json Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -33,6 +33,7 @@ export type HTMLOptions = {
autoRefreshCSS?: boolean; autoRefreshCSS?: boolean;
disableCopyEvent?: boolean; disableCopyEvent?: boolean;
language?: string; language?: string;
bodyClasses?: string[];
}; };
export default function html( export default function html(

View File

@ -1,9 +1,5 @@
import type { FlatTemplatable } from "tempstream";
import { TempstreamJSX } from "tempstream"; import { TempstreamJSX } from "tempstream";
import type { import type { ComponentToHTMLArgs } from "@sealcode/jdd";
ExtractStructuredComponentArgumentsParsed,
JDDContext,
} from "@sealcode/jdd";
import { Component, ComponentArguments } from "@sealcode/jdd"; import { Component, ComponentArguments } from "@sealcode/jdd";
import arrow from "./autoscrolling-images-arrow.svg"; import arrow from "./autoscrolling-images-arrow.svg";
@ -25,15 +21,10 @@ export class AutoscrollingImages extends Component<typeof component_arguments> {
return component_arguments; return component_arguments;
} }
toHTML( toHTML({
{ args: { title, interval, imagesPerPage, images },
title, jdd_context: { render_image },
interval, }: ComponentToHTMLArgs<typeof component_arguments>) {
imagesPerPage,
images,
}: ExtractStructuredComponentArgumentsParsed<typeof component_arguments>,
{ render_image }: JDDContext
): FlatTemplatable {
const imageNumberPerPage = parseInt(imagesPerPage); const imageNumberPerPage = parseInt(imagesPerPage);
const parsedImagesArray = []; const parsedImagesArray = [];

View File

@ -1,10 +1,6 @@
import type { FlatTemplatable } from "tempstream"; import type { FlatTemplatable } from "tempstream";
import { TempstreamJSX } from "tempstream"; import { TempstreamJSX } from "tempstream";
import type { import type { ComponentToHTMLArgs, ExtractParsed, JDDContext } from "@sealcode/jdd";
ExtractParsed,
ExtractStructuredComponentArgumentsParsed,
JDDContext,
} from "@sealcode/jdd";
import { Component, ComponentArguments } from "@sealcode/jdd"; import { Component, ComponentArguments } from "@sealcode/jdd";
type ExtractArray<T> = T extends Array<infer X> ? X : never; type ExtractArray<T> = T extends Array<infer X> ? X : never;
@ -123,13 +119,10 @@ export class DynamicGrid extends Component<typeof component_arguments> {
); );
} }
toHTML( toHTML({
{ args: { heading, tabs },
heading, jdd_context,
tabs, }: ComponentToHTMLArgs<typeof component_arguments>): FlatTemplatable {
}: ExtractStructuredComponentArgumentsParsed<typeof component_arguments>,
jdd_context: JDDContext
): FlatTemplatable {
const { value: id } = generate_id.next(); const { value: id } = generate_id.next();
return ( return (
<div class="dynamic-grid-component"> <div class="dynamic-grid-component">

View File

@ -1,9 +1,6 @@
import type { FlatTemplatable } from "tempstream"; import type { FlatTemplatable } from "tempstream";
import { TempstreamJSX } from "tempstream"; import { TempstreamJSX } from "tempstream";
import type { import type { ComponentToHTMLArgs } from "@sealcode/jdd";
ExtractStructuredComponentArgumentsParsed,
JDDContext,
} from "@sealcode/jdd";
import { Component, ComponentArguments } from "@sealcode/jdd"; import { Component, ComponentArguments } from "@sealcode/jdd";
const component_arguments = { const component_arguments = {
@ -29,15 +26,10 @@ export class FaqComponent extends Component<typeof component_arguments> {
return component_arguments; return component_arguments;
} }
toHTML( toHTML({
{ args: { title, content, faq, button },
title, jdd_context: { render_markdown, language },
content, }: ComponentToHTMLArgs<typeof component_arguments>): FlatTemplatable {
faq,
button,
}: ExtractStructuredComponentArgumentsParsed<typeof component_arguments>,
{ render_markdown }: JDDContext
): FlatTemplatable {
const buttonText = button.buttonText.toUpperCase(); const buttonText = button.buttonText.toUpperCase();
return ( return (
@ -46,7 +38,7 @@ export class FaqComponent extends Component<typeof component_arguments> {
<div class="title-container"> <div class="title-container">
<div class="title">{title} </div> <div class="title">{title} </div>
<div class="content"> <div class="content">
{render_markdown(content.text)}{" "} {render_markdown(language, content.text)}{" "}
<strong>{content.number}</strong> <strong>{content.number}</strong>
</div> </div>
</div> </div>
@ -56,7 +48,7 @@ export class FaqComponent extends Component<typeof component_arguments> {
<details class="question-container" open={index == 0}> <details class="question-container" open={index == 0}>
<summary class="question">{element.question}</summary> <summary class="question">{element.question}</summary>
<div class="answer"> <div class="answer">
<p>{render_markdown(element.answer)}</p> <p>{render_markdown(language, element.answer)}</p>
</div> </div>
</details> </details>
))} ))}

View File

@ -1,9 +1,6 @@
import type { FlatTemplatable } from "tempstream"; import type { FlatTemplatable } from "tempstream";
import { TempstreamJSX } from "tempstream"; import { TempstreamJSX } from "tempstream";
import type { import type { ComponentToHTMLArgs } from "@sealcode/jdd";
ExtractStructuredComponentArgumentsParsed,
JDDContext,
} from "@sealcode/jdd";
import { Component, ComponentArguments } from "@sealcode/jdd"; import { Component, ComponentArguments } from "@sealcode/jdd";
const component_arguments = { const component_arguments = {
@ -24,15 +21,10 @@ export class HeaderWithImage extends Component<typeof component_arguments> {
return component_arguments; return component_arguments;
} }
toHTML( toHTML({
{ args: { title, content, image_with_alt, button },
title, jdd_context: { render_markdown, render_image, language },
content, }: ComponentToHTMLArgs<typeof component_arguments>): FlatTemplatable {
image_with_alt,
button,
}: ExtractStructuredComponentArgumentsParsed<typeof component_arguments>,
{ render_markdown, render_image }: JDDContext
): FlatTemplatable {
const buttonText = button.text.toUpperCase(); const buttonText = button.text.toUpperCase();
const titleUpperCase = title.toUpperCase(); const titleUpperCase = title.toUpperCase();
@ -49,7 +41,7 @@ export class HeaderWithImage extends Component<typeof component_arguments> {
<div class="title-wrapper"> <div class="title-wrapper">
<h2 class="title">{titleUpperCase}</h2> <h2 class="title">{titleUpperCase}</h2>
</div> </div>
<div class="content">{render_markdown(content)}</div> <div class="content">{render_markdown(language, content)}</div>
{button.text === "" ? null : ( {button.text === "" ? null : (
<div class="button-container"> <div class="button-container">
<a class="button" href={button.link}> <a class="button" href={button.link}>

View File

@ -1,9 +1,6 @@
import type { FlatTemplatable } from "tempstream"; import type { FlatTemplatable } from "tempstream";
import { TempstreamJSX } from "tempstream"; import { TempstreamJSX } from "tempstream";
import type { import type { ComponentToHTMLArgs } from "@sealcode/jdd";
ExtractStructuredComponentArgumentsParsed,
JDDContext,
} from "@sealcode/jdd";
import { Component, ComponentArguments } from "@sealcode/jdd"; import { Component, ComponentArguments } from "@sealcode/jdd";
const component_arguments = { const component_arguments = {
@ -19,13 +16,10 @@ export class ImageDemo extends Component<typeof component_arguments> {
return component_arguments; return component_arguments;
} }
toHTML( toHTML({
{ args: { image_with_alt, multiple_images },
image_with_alt, jdd_context: { render_image },
multiple_images, }: ComponentToHTMLArgs<typeof component_arguments>): FlatTemplatable {
}: ExtractStructuredComponentArgumentsParsed<typeof component_arguments>,
{ render_image }: JDDContext
): FlatTemplatable {
return ( return (
<div class="image-demo"> <div class="image-demo">
<h2>Image with alt text</h2> <h2>Image with alt text</h2>

View File

@ -1,6 +1,5 @@
import type { FlatTemplatable } from "tempstream";
import { TempstreamJSX } from "tempstream"; import { TempstreamJSX } from "tempstream";
import type { ExtractStructuredComponentArgumentsParsed } from "@sealcode/jdd"; import type { ComponentToHTMLArgs } from "@sealcode/jdd";
import { Component, ComponentArguments } from "@sealcode/jdd"; import { Component, ComponentArguments } from "@sealcode/jdd";
const coordinates = new ComponentArguments.ShortText(); const coordinates = new ComponentArguments.ShortText();
@ -49,11 +48,7 @@ export class MapWithPins extends Component<typeof component_arguments> {
]; ];
} }
toHTML({ toHTML({ args: { pins } }: ComponentToHTMLArgs<typeof component_arguments>) {
pins,
}: ExtractStructuredComponentArgumentsParsed<
typeof component_arguments
>): FlatTemplatable {
return ( return (
<div <div
class="map-with-pins" class="map-with-pins"

View File

@ -1,8 +1,5 @@
import { TempstreamJSX } from "tempstream"; import { TempstreamJSX } from "tempstream";
import type { import type { ComponentToHTMLArgs } from "@sealcode/jdd";
ExtractStructuredComponentArgumentsParsed,
JDDContext,
} from "@sealcode/jdd";
import { Component, ComponentArguments } from "@sealcode/jdd"; import { Component, ComponentArguments } from "@sealcode/jdd";
import type { Readable } from "stream"; import type { Readable } from "stream";
@ -22,18 +19,15 @@ export class NiceBox extends Component<typeof component_arguments> {
return component_arguments; return component_arguments;
} }
async toHTML( async toHTML({
{ args: { title, content, images },
title, classes,
content, jdd_context: { render_markdown, render_image, language },
images, }: ComponentToHTMLArgs<typeof component_arguments>): Promise<Readable> {
}: ExtractStructuredComponentArgumentsParsed<typeof component_arguments>,
{ render_markdown, render_image }: JDDContext
): Promise<Readable> {
return ( return (
<div class="nice-box"> <div class={["nice-box", ...classes]}>
<h2>{title}</h2> <h2>{title}</h2>
<div>{render_markdown(content)}</div> <div>{render_markdown(language, content)}</div>
{images.map((image) => {images.map((image) =>
render_image(image.image, { render_image(image.image, {
container: { container: {

View File

@ -1,6 +1,5 @@
import type { FlatTemplatable } from "tempstream";
import { TempstreamJSX } from "tempstream"; import { TempstreamJSX } from "tempstream";
import type { ExtractStructuredComponentArgumentsParsed } from "@sealcode/jdd"; import type { ComponentToHTMLArgs } from "@sealcode/jdd";
import { Component, ComponentArguments, isTableHeader } from "@sealcode/jdd"; import { Component, ComponentArguments, isTableHeader } from "@sealcode/jdd";
const component_arguments = { const component_arguments = {
@ -23,11 +22,7 @@ export class Table extends Component<typeof component_arguments> {
return component_arguments; return component_arguments;
} }
toHTML({ toHTML({ args: { table } }: ComponentToHTMLArgs<typeof component_arguments>) {
table,
}: ExtractStructuredComponentArgumentsParsed<
typeof component_arguments
>): FlatTemplatable {
return ( return (
<div class="table"> <div class="table">
<table> <table>

View File

@ -7,6 +7,7 @@ import { imageRouter } from "./image-router.js";
export function makeJDDContext(ctx: BaseContext): JDDContext { export function makeJDDContext(ctx: BaseContext): JDDContext {
return { return {
language: "pl",
...makeSimpleJDDContext(TheFileManager), ...makeSimpleJDDContext(TheFileManager),
render_image: async (image: string | FilePointer | null, args) => { render_image: async (image: string | FilePointer | null, args) => {
if (!image) { if (!image) {

View File

@ -0,0 +1,13 @@
/* eslint-disable @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-assignment */
import { Controller } from "stimulus";
export default class AutogrowTextarea extends Controller<HTMLTextAreaElement> {
connect() {
this.autogrow();
}
autogrow() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
(this.element.parentNode as any).dataset.replicatedValue = this.element.value;
}
}

View File

@ -1,6 +1,16 @@
/* eslint-disable @typescript-eslint/consistent-type-assertions */
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
import { Controller } from "stimulus"; import { Controller } from "stimulus";
export default class ComponentDebugger extends Controller { export default class ComponentDebugger extends Controller {
declare gutterTarget: HTMLDivElement;
declare checkboxTarget: HTMLInputElement;
declare checkboxTargets: HTMLInputElement[];
declare previewTarget: HTMLDivElement;
declare componentBlockTargets: HTMLDivElement[];
declare componentBlockTargetDisconnected: (e: HTMLDivElement) => void;
static targets = ["gutter", "componentBlock", "checkbox", "preview"];
id: string; id: string;
main_form: HTMLFormElement; main_form: HTMLFormElement;
is_resizing = false; is_resizing = false;
@ -26,7 +36,7 @@ export default class ComponentDebugger extends Controller {
document.addEventListener("turbo:render", () => this.update_width_display()); document.addEventListener("turbo:render", () => this.update_width_display());
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const gutter = this.targets.find("gutter") as HTMLDivElement; const gutter = this.gutterTarget;
gutter.addEventListener("mousedown", (e) => { gutter.addEventListener("mousedown", (e) => {
this.is_resizing = true; this.is_resizing = true;
this.origin_x = e.clientX; this.origin_x = e.clientX;
@ -74,4 +84,82 @@ export default class ComponentDebugger extends Controller {
const value = dropdown.value; const value = dropdown.value;
this.setPreviewWidth(parseInt(value)); this.setPreviewWidth(parseInt(value));
} }
componentBlockTargetConnected(block_element: HTMLDivElement) {
const index = parseInt(block_element.getAttribute("data-component-index"));
block_element.addEventListener("focusin", () => {
this.scrollToComponentPreview(index);
});
}
previewTargetConnected(preview_element: HTMLDivElement) {
preview_element.addEventListener("click", ({ target }) => {
if (!(target instanceof HTMLElement)) {
return;
}
const closest = target.closest(".jdd-component");
if (!closest) {
return;
}
const index = parseInt(
Array.from(closest.classList)
.find((c) => c.startsWith("component-number-"))
?.replace("component-number-", "")
);
if (isNaN(index)) {
return;
}
this.focusComponentBlock(index);
});
}
focusComponentBlock(index: number) {
const block = this.componentBlockTargets[index];
if (!block) {
return;
}
this.checkboxTargets[index].checked = true;
block.scrollIntoView({ behavior: "smooth" });
(
block.querySelector(".component-preview-parameters input") as HTMLInputElement
)?.focus();
}
getIndex(block_element: HTMLDivElement) {
const index = parseInt(block_element.getAttribute("data-component-index"));
return index;
}
labelClicked(element: MouseEvent) {
const block_element = (element.target as HTMLDivElement).closest(
`[data-component-debugger-target="componentBlock"]`
) as HTMLDivElement;
const index = this.getIndex(block_element);
if (!this.checkboxTargets?.[index].checked) {
this.scrollToComponentPreview(index);
}
}
getPreviewElementForComponentIndex(index: number) {
const element = this.element.querySelector(
`.component-number-${index}`
) as HTMLDialogElement;
return element;
}
scrollToComponentPreview(index: number) {
const element = this.getPreviewElementForComponentIndex(index);
if (!element) {
return;
}
const preview_element = this.element.querySelector(".component-preview");
if (element.clientHeight > preview_element.clientHeight) {
preview_element.scrollTop = element.offsetTop - 44;
} else {
preview_element.scrollTop =
element.offsetTop -
(preview_element.clientHeight - element.clientHeight) / 2 -
44;
}
}
} }

View File

@ -14,6 +14,8 @@ export function ComponentInputImage<State extends JDDPageState>({
arg, arg,
value, value,
ctx, ctx,
page,
state,
}: { }: {
state: State; state: State;
arg_path: string[]; arg_path: string[];
@ -30,13 +32,16 @@ export function ComponentInputImage<State extends JDDPageState>({
data-controller="input-image-preview" data-controller="input-image-preview"
> >
{arg_path.at(-1) || ""} {arg_path.at(-1) || ""}
{value && <div class="image-preview-container">
jdd_context.render_image(value, { {value &&
container: { width: 40, height: 40, objectFit: "cover" }, jdd_context.render_image(value, {
crop: { width: 40, height: 40 }, container: { width: 40, height: 40, objectFit: "cover" },
style: "height: 40px; width: 40px;", crop: { width: 40, height: 40 },
alt: "", style: "height: 40px; width: 40px;",
})} alt: "",
})}
</div>
<input <input
type="file" type="file"
name={`$${printArgPath(arg_path)}.new`} name={`$${printArgPath(arg_path)}.new`}
@ -54,6 +59,14 @@ export function ComponentInputImage<State extends JDDPageState>({
autocomplete="off" autocomplete="off"
/> />
</div> </div>
{page.makeActionButton(
state,
{
action: "remove_file",
label: "❌",
},
arg_path
)}
</div> </div>
); );
} }

View File

@ -0,0 +1,39 @@
import type { BaseContext } from "koa";
import type { SingleReference } from "@sealcode/jdd";
import { TempstreamJSX } from "tempstream";
import { makeJDDContext } from "../../jdd-context.js";
import { printArgPath } from "./print-arg-path.js";
export async function ComponentInputSingleReference<
State,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
T extends SingleReference<any>
>({
ctx,
arg_path,
arg,
value,
onchange,
}: {
ctx: BaseContext;
state: State;
arg_path: string[];
arg: T;
value: string;
onchange?: string;
}): Promise<import("stream").Readable> {
return (
<div>
<label>
{arg_path.at(-1) || ""}
<select name={`$${printArgPath(arg_path)}`} onchange={onchange}>
{(await arg.getValues(makeJDDContext(ctx))).map((v) => (
<option value={v.value} selected={value == v.value}>
{v.label}
</option>
))}
</select>
</label>
</div>
);
}

View File

@ -3,6 +3,7 @@ import { isTableHeader } from "@sealcode/jdd";
import type { StatefulPage } from "@sealcode/sealgen"; import type { StatefulPage } from "@sealcode/sealgen";
import type { BaseContext } from "koa"; import type { BaseContext } from "koa";
import { TempstreamJSX } from "tempstream"; import { TempstreamJSX } from "tempstream";
import { makeJDDContext } from "../../jdd-context.js";
import { ComponentInput } from "./component-input.js"; import { ComponentInput } from "./component-input.js";
import type { ComponentPreviewActions } from "./component-preview-actions.js"; import type { ComponentPreviewActions } from "./component-preview-actions.js";
import type { JDDPageState } from "./jdd-page.js"; import type { JDDPageState } from "./jdd-page.js";
@ -34,7 +35,7 @@ export async function ComponentInputTable<
page: StatefulPage<JDDPageState, typeof ComponentPreviewActions>; page: StatefulPage<JDDPageState, typeof ComponentPreviewActions>;
}): Promise<import("stream").Readable> { }): Promise<import("stream").Readable> {
if (!value) { if (!value) {
value = arg.getEmptyValue(); value = await arg.getEmptyValue(makeJDDContext(ctx));
} }
return ( return (

View File

@ -1,6 +1,7 @@
import { printArgPath } from "./print-arg-path.js"; import { printArgPath } from "./print-arg-path.js";
import type { BaseContext } from "koa"; import type { BaseContext } from "koa";
import type { ComponentArgument, TableData } from "@sealcode/jdd"; import type { ComponentArgument, TableData } from "@sealcode/jdd";
import { SingleReference } from "@sealcode/jdd";
import { ComponentArguments, Enum, Image, List, Structured, Table } from "@sealcode/jdd"; import { ComponentArguments, Enum, Image, List, Structured, Table } from "@sealcode/jdd";
import { ComponentInputStructured } from "./component-input-structured.js"; import { ComponentInputStructured } from "./component-input-structured.js";
import type { StatefulPage } from "@sealcode/sealgen"; import type { StatefulPage } from "@sealcode/sealgen";
@ -14,11 +15,13 @@ import { ComponentInputTable } from "./component-input-table.js";
import { TempstreamJSX } from "tempstream"; import { TempstreamJSX } from "tempstream";
import type { FilePointer } from "@sealcode/file-manager"; import type { FilePointer } from "@sealcode/file-manager";
import { is, predicates } from "@sealcode/ts-predicates"; import { is, predicates } from "@sealcode/ts-predicates";
import { makeJDDContext } from "../../jdd-context.js";
import { ComponentInputSingleReference } from "./component-input-single-reference.js";
export const actionName = "Components"; export const actionName = "Components";
const absoluteUrlPattern = "http(s?)(://)((www.)?)(([^.]+).)?([a-zA-z0-9-_]+)"; const absoluteUrlPattern = "http(s?)(://)((www.)?)(([^.]+).)?([a-zA-z0-9-_]+)";
export function ComponentInput<State extends JDDPageState, T>({ export async function ComponentInput<State extends JDDPageState, T>({
ctx, ctx,
state, state,
arg_path, arg_path,
@ -32,9 +35,9 @@ export function ComponentInput<State extends JDDPageState, T>({
arg: ComponentArgument<T>; arg: ComponentArgument<T>;
value: T; value: T;
page: StatefulPage<JDDPageState, typeof ComponentPreviewActions>; page: StatefulPage<JDDPageState, typeof ComponentPreviewActions>;
}): Readable | Promise<Readable> { }): Promise<Readable> {
if (value === undefined) { if (value === undefined) {
value = arg.getEmptyValue(); value = await arg.getEmptyValue(makeJDDContext(ctx));
} }
if (arg instanceof List) { if (arg instanceof List) {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@ -66,6 +69,18 @@ export function ComponentInput<State extends JDDPageState, T>({
}); });
} }
if (arg instanceof SingleReference) {
return ComponentInputSingleReference({
ctx,
state,
arg_path,
arg,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
value: value as string,
onchange: page.rerender(),
});
}
if (arg instanceof Enum) { if (arg instanceof Enum) {
return ComponentInputEnum({ return ComponentInputEnum({
state, state,
@ -106,13 +121,21 @@ export function ComponentInput<State extends JDDPageState, T>({
<label> <label>
{arg_path.at(-1) || ""} {arg_path.at(-1) || ""}
{argType == "markdown" ? ( {argType == "markdown" ? (
<textarea <div
name={`$${printArgPath(arg_path)}`} class="grow-wrap"
onblur={page.rerender()} data-replicated-value={is(value, predicates.string) ? value : ""}
cols="40"
> >
{is(value, predicates.string) ? value : ""} {/* putting the content in the attribute to enable autogrow */}
</textarea> <textarea
name={`$${printArgPath(arg_path)}`}
onblur={page.rerender()}
cols="40"
data-controller="autogrow-textarea submit-on-input paste-to-markdown"
data-action="autogrow-textarea#autogrow blur->autogrow-textarea#autogrow resize->autogrow-textarea#autogrow submit-on-input#sendValues focus->submit-on-input#makePermanent blur->submit-on-input#makeNotPermanent"
>
{is(value, predicates.string) ? value : ""}
</textarea>
</div>
) : ( ) : (
<input <input
type={inputType} type={inputType}

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/consistent-type-assertions */
import type { Registry, TableData } from "@sealcode/jdd"; import type { Registry, TableData } from "@sealcode/jdd";
import { List, Table } from "@sealcode/jdd"; import { List, Table } from "@sealcode/jdd";
import type { BaseContext } from "koa"; import type { BaseContext } from "koa";
@ -18,9 +19,13 @@ export function getComponentData(
arg_path: string[], arg_path: string[],
registry: Registry registry: Registry
) { ) {
const component_index = parseInt(arg_path[1]); const index_arg = arg_path[1];
const component_args = state.components[component_index].args; if (!index_arg) {
const component_name = state.components[component_index].component_name; throw new Error("Missing component index in arg path");
}
const component_index = parseInt(index_arg);
const component_args = state.components[component_index]?.args || {};
const component_name = state.components[component_index]?.component_name || "";
const component = registry.get(component_name); const component = registry.get(component_name);
const arg_path_within_component = arg_path.slice(3); // remove "components" and the index of the component and "args" const arg_path_within_component = arg_path.slice(3); // remove "components" and the index of the component and "args"
@ -94,7 +99,7 @@ export const ComponentPreviewActions = <const>{
change_component: async ( change_component: async (
ctx: BaseContext, ctx: BaseContext,
state: JDDPageState, _state: JDDPageState,
inputs: Record<string, unknown> inputs: Record<string, unknown>
): Promise<JDDPageState> => { ): Promise<JDDPageState> => {
const component_name = inputs.component; const component_name = inputs.component;
@ -128,7 +133,11 @@ export const ComponentPreviewActions = <const>{
registry registry
); );
state.components[component_index].args = const component_data = state.components[component_index];
if (!component_data) {
throw new Error("Missing component data");
}
component_data.args =
(await component?.getExampleValues(makeJDDContext(ctx))) || {}; (await component?.getExampleValues(makeJDDContext(ctx))) || {};
return { return {
...state, ...state,
@ -304,6 +313,9 @@ export const ComponentPreviewActions = <const>{
inputs: Record<string, string> inputs: Record<string, string>
): Promise<JDDPageState> => { ): Promise<JDDPageState> => {
const component_name = inputs.component; const component_name = inputs.component;
if (!component_name) {
throw new Error("Missing component name");
}
const component = registry.get(component_name); const component = registry.get(component_name);
return { return {
@ -339,9 +351,12 @@ export const ComponentPreviewActions = <const>{
component_index: number component_index: number
): Promise<JDDPageState> => { ): Promise<JDDPageState> => {
const newComps = [...state.components]; const newComps = [...state.components];
// prettier-ignore const prev = newComps[component_index - 1];
[newComps[component_index], newComps[component_index - 1]] = const curr = newComps[component_index];
[newComps[component_index - 1], newComps[component_index]]; if (!prev || !curr) {
throw new Error("No component at such index or cannot move it up");
}
[newComps[component_index], newComps[component_index - 1]] = [prev, curr];
return { ...state, components: newComps }; return { ...state, components: newComps };
}, },
@ -352,9 +367,21 @@ export const ComponentPreviewActions = <const>{
component_index: number component_index: number
): Promise<JDDPageState> => { ): Promise<JDDPageState> => {
const newComps = [...state.components]; const newComps = [...state.components];
// prettier-ignore const next = newComps[component_index + 1];
[newComps[component_index], newComps[component_index + 1]] = const curr = newComps[component_index];
[newComps[component_index + 1], newComps[component_index]]; if (!next || !curr) {
throw new Error("No component at such index or cannot move it up");
}
[newComps[component_index], newComps[component_index + 1]] = [next, curr];
return { ...state, components: newComps }; return { ...state, components: newComps };
}, },
remove_file: async (
_ctx: BaseContext,
state: JDDPageState,
_: Record<string, string>,
arg_path: string[]
): Promise<JDDPageState> => {
objectPath.set(state, arg_path, null);
return state;
},
}; };

View File

@ -0,0 +1,124 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/consistent-type-assertions */
/* eslint-disable @typescript-eslint/no-unused-vars */
import type Router from "@koa/router";
import type { JDDocumentContainer, RawJDDocument } from "@sealcode/jdd";
import {
documentContainerFromStorage,
documentToParsed,
documentToStorage,
} from "@sealcode/jdd";
import type { BaseContext } from "koa";
import type { Collection, CollectionItem, FieldNames } from "sealious";
import { TempstreamJSX } from "tempstream";
import { registry } from "../../jdd-components/registry.js";
import { makeJDDContext } from "../../jdd-context.js";
import JDDCreator from "./jdd-creator.js";
import type { JDDPageState } from "./jdd-page.js";
export const actionName = "ArticleContentEdit";
export abstract class EditJDDField<C extends Collection> extends JDDCreator {
async getID(ctx: BaseContext): Promise<string> {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const id = ctx.params["id"] as string;
if (!id) {
throw new Error("Missing URL parameter: " + "id");
}
return id;
}
abstract getCollection(ctx: BaseContext): C;
async getItem(ctx: BaseContext): Promise<CollectionItem<C>> {
const {
items: [item],
} = await this.getCollection(ctx)
.list(ctx.$context)
.ids([await this.getID(ctx)])
.fetch();
if (!item) {
throw new Error("Couldn't get item of id " + (await this.getID(ctx)));
}
return item;
}
abstract getJDDFieldName(): FieldNames<C["fields"]>;
mount(router: Router, path: string) {
super.mount(router, path);
router.post(path + "save/", async (ctx) => {
const { state } = await this.extractState(ctx);
const item = await this.getItem(ctx);
item.set(
this.getJDDFieldName(),
(
await documentToStorage(registry, makeJDDContext(ctx), {
value: state.components,
} as unknown as JDDocumentContainer<"parsed">)
).value as any
);
await item.save(ctx.$context);
ctx.type = "html";
ctx.status = 422;
if (!state.messages) {
state.messages = [];
}
state.messages.push("Saved!");
ctx.body = this.render(ctx, state);
});
}
async renderHeader(_ctx: BaseContext, _item: CollectionItem<C>) {
return <h1>Edit JDD</h1>;
}
async renderPreParameterButtons(ctx: BaseContext) {
const item = await this.getItem(ctx);
return <div>{this.renderHeader(ctx, item)}</div>;
}
renderParameterButtons(state: JDDPageState) {
{
/*The below button has to be here in order for it to be the default behavior */
}
return (
<div>
<input type="submit" value="Preview" />
<select name="component">
{Object.keys(this.getRegistryComponents()).map((cmp) => (
<option value={cmp}>{cmp}</option>
))}
</select>
{this.makeActionButton(state, {
action: "add_component",
label: "Add component",
})}
<input type="submit" formaction="./save/" value="zapisz" />
</div>
);
}
async getInitialState(ctx: BaseContext) {
const article = await this.getItem(ctx);
const parsed_document = await documentToParsed(
registry,
makeJDDContext(ctx),
documentContainerFromStorage(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
(article.get(this.getJDDFieldName()) as RawJDDocument) || []
)
);
return {
components: parsed_document.value.map((e) => ({ ...e, open: true })),
};
}
// uncomment to create whitelist of allowed components
// getAllowedComponents() {
// return ["nice-box"];
// }
}

View File

@ -0,0 +1,33 @@
/* https://chriscoyier.net/2023/09/29/css-solves-auto-expanding-textareas-probably-eventually/ */
.grow-wrap {
/* easy way to plop the elements on top of each other and have them both sized based on the tallest one's height */
display: grid;
}
.grow-wrap::after {
/* Note the weird space! Needed to preventy jumpy behavior */
content: attr(data-replicated-value) " ";
/* This is how textarea text behaves */
white-space: pre-wrap;
/* Hidden from view, clicks, and screen readers */
visibility: hidden;
}
.grow-wrap > textarea {
/* You could leave this, but after a user resizes, then it ruins the auto sizing */
resize: none;
/* Firefox shows scrollbar on growth, you can hide like this. */
overflow: hidden;
}
.grow-wrap > textarea,
.grow-wrap::after {
/* Identical styling required!! */
border: 1px solid black;
padding: 0.5rem;
font: inherit;
/* Place on top of each other */
grid-area: 1 / 1 / 2 / 2;
}

View File

@ -4,7 +4,12 @@ export default class InputImagePreview extends Controller {
id: string; id: string;
handleChange() { handleChange() {
const img = this.element.querySelector("img"); let img = this.element.querySelector("img");
if (!img) {
img = document.createElement("img");
img.setAttribute("style", "height: 40px; width: 40px");
this.element.querySelector(".image-preview-container").appendChild(img);
}
window.URL.revokeObjectURL(img.src); window.URL.revokeObjectURL(img.src);
const new_url = window.URL.createObjectURL( const new_url = window.URL.createObjectURL(
this.element.querySelector("input").files[0] this.element.querySelector("input").files[0]

View File

@ -20,8 +20,8 @@ export default abstract class JDDCreator extends JDDPage {
return []; return [];
} }
getRegistryCompoments() { getRegistryComponents() {
const all_components = super.getRegistryCompoments(); const all_components = super.getRegistryComponents();
const allowed_components = this.getAllowedComponents(); const allowed_components = this.getAllowedComponents();
if (allowed_components.length > 0) { if (allowed_components.length > 0) {
@ -43,7 +43,7 @@ export default abstract class JDDCreator extends JDDPage {
<div> <div>
<input type="submit" value="Preview" /> <input type="submit" value="Preview" />
<select name="component"> <select name="component">
{Object.keys(this.getRegistryCompoments()).map((cmp) => ( {Object.keys(this.getRegistryComponents()).map((cmp) => (
<option value={cmp}>{cmp}</option> <option value={cmp}>{cmp}</option>
))} ))}
</select> </select>
@ -64,42 +64,71 @@ export default abstract class JDDCreator extends JDDPage {
}, },
component_index: number component_index: number
) { ) {
const checkbox_id = `component_${component_index}_open`;
return ( return (
<fieldset> <div
<legend>Component - {component.component_name}</legend> class={[
{this.makeActionButton( "jdd-editor__component-block",
state, "jdd-editor__component-block--number-" + component_index,
{ action: "remove_component", label: "❌" }, ]}
component_index data-component-debugger-target="componentBlock"
)} data-component-index={component_index.toString()}
{this.makeActionButton( >
state, <summary class="jdd-editor__component-block__top_bar">
{ {this.makeActionButton(
action: "move_component_up", state,
label: "Move this row up", { action: "remove_component", label: "❌" },
content: /* HTML */ `<img component_index
width="20" )}
height="20" {this.makeActionButton(
src="${move_row_up_icon.url}" state,
/>`, {
}, action: "move_component_up",
component_index label: "Move this row up",
)} content: /* HTML */ `<img
{this.makeActionButton( width="20"
state, height="20"
{ src="${move_row_up_icon.url}"
action: "move_component_down", />`,
label: "Move this row down", },
content: /* HTML */ `<img component_index
width="20" )}
height="20" {this.makeActionButton(
src="${move_row_down_icon.url}" state,
/>`, {
}, action: "move_component_down",
component_index label: "Move this row down",
)} content: /* HTML */ `<img
{super.renderComponentBlock(ctx, state, component, component_index)} width="20"
</fieldset> height="20"
src="${move_row_down_icon.url}"
/>`,
},
component_index
)}
<span>{component.component_name}</span>
<label
class="component-block__handle"
for={checkbox_id}
style="flex-grow: 1"
data-action="click->component-debugger#labelClicked"
>
<span class="jdd-editor__component-block__chevron"> &gt; </span>
</label>
</summary>
<input
type="checkbox"
class="component-collapse-toggle"
name={`$[components][${component_index}][open]`}
data-turbo-permanent
id={checkbox_id}
style="display:none"
data-component-debugger-target="checkbox"
/>
<div class="jdd-editor__component-block__inner">
{super.renderComponentBlock(ctx, state, component, component_index)}
</div>
</div>
); );
} }
} }

View File

@ -17,6 +17,7 @@ export const actionName = "Components";
export type JDDPageState = { export type JDDPageState = {
components: RawJDDocument; components: RawJDDocument;
preview_size?: string; preview_size?: string;
messages?: string[];
}; };
export default abstract class JDDPage extends StatefulPage< export default abstract class JDDPage extends StatefulPage<
@ -27,14 +28,17 @@ export default abstract class JDDPage extends StatefulPage<
previewSizes = ["320", "600", "800", "1024", "1300", "1920"]; previewSizes = ["320", "600", "800", "1024", "1300", "1920"];
getRegistryCompoments() { getRegistryComponents() {
return registry.getAll(); return registry.getAll();
} }
async getInitialState(ctx: BaseContext) { async getInitialState(ctx: BaseContext) {
const [component_name, component] = Object.entries( const all_components = Object.entries(this.getRegistryComponents());
this.getRegistryCompoments() const first_component = all_components[0];
)[0]; if (!first_component) {
throw new Error("No defined components!");
}
const [component_name, component] = first_component;
const initial_state = { const initial_state = {
components: [ components: [
{ {
@ -60,6 +64,7 @@ export default abstract class JDDPage extends StatefulPage<
preserveScroll: true, preserveScroll: true,
autoRefreshCSS: true, autoRefreshCSS: true,
navbar: () => ``, navbar: () => ``,
bodyClasses: ["jdd-editor"],
}, },
(...args) => (...args) =>
tempstream`${defaultHead(...args)}${renderEarlyAssets( tempstream`${defaultHead(...args)}${renderEarlyAssets(
@ -96,8 +101,9 @@ export default abstract class JDDPage extends StatefulPage<
if (!component) { if (!component) {
throw new Error(`Unknown component: ${component_name}`); throw new Error(`Unknown component: ${component_name}`);
} }
const overrides_for_component = const overrides_for_component = overrides.components[
overrides.components[parseInt(component_index)]; parseInt(component_index)
] || { args: {} };
const promises = Object.entries(component.getArguments()).map( const promises = Object.entries(component.getArguments()).map(
async ([arg_name, arg]) => { async ([arg_name, arg]) => {
const value = overrides_for_component.args[arg_name]; const value = overrides_for_component.args[arg_name];
@ -126,8 +132,7 @@ export default abstract class JDDPage extends StatefulPage<
) { ) {
const jdd_context = makeJDDContext(ctx); const jdd_context = makeJDDContext(ctx);
return ( return (
<fieldset class="component-preview-parameters"> <div class="component-preview-parameters">
<legend>Parameters</legend>
{Object.entries(component.getArguments()).map(async ([arg_name, arg]) => ( {Object.entries(component.getArguments()).map(async ([arg_name, arg]) => (
<ComponentInput <ComponentInput
{...{ {...{
@ -144,8 +149,7 @@ export default abstract class JDDPage extends StatefulPage<
}} }}
/> />
))} ))}
<input type="submit" value="Preview" /> </div>
</fieldset>
); );
} }
@ -237,6 +241,25 @@ export default abstract class JDDPage extends StatefulPage<
return result; return result;
} }
renderPreParameterButtons(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_ctx: BaseContext,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_state: JDDPageState
): FlatTemplatable | Promise<FlatTemplatable> {
return "";
}
renderMessages(_ctx: BaseContext, state: JDDPageState) {
return (
<ul>
{(state.messages || []).map((e) => (
<li>{e}</li>
))}
</ul>
);
}
render(ctx: BaseContext, state: JDDPageState) { render(ctx: BaseContext, state: JDDPageState) {
return ( return (
<div <div
@ -246,51 +269,46 @@ export default abstract class JDDPage extends StatefulPage<
data-controller="component-debugger" data-controller="component-debugger"
> >
<div class="component-arguments"> <div class="component-arguments">
{this.renderPreParameterButtons(ctx, state)}
{this.renderParameterButtons(state)} {this.renderParameterButtons(state)}
{this.renderMessages(ctx, state)}
{state.components.map((component, component_index) => {state.components.map((component, component_index) =>
this.renderComponentBlock(ctx, state, component, component_index) this.renderComponentBlock(ctx, state, component, component_index)
)} )}
<code>{this.serializeState(ctx, state)}</code> <code style="max-height: 100px; display: block; overflow: hidden; font-size: 9px; color: #a8a8a8; padding: 1rem;">
{this.serializeState(ctx, state)}
</code>
</div> </div>
<div class="resize-gutter" data-component-debugger-target="gutter"></div> <div class="resize-gutter" data-component-debugger-target="gutter"></div>
<div class="component-preview" data-component-debugger-target="preview"> <div class="component-preview" data-component-debugger-target="preview">
<fieldset> <div class="component-preview__header">
<legend> <span>Preview</span>
Preview{" "} <span data-component-debugger-target="component-width"></span>
<span data-component-debugger-target="component-width"></span> <select
<select name="size"
name="size" autocomplete="off"
autocomplete="off" class="component-preview-size-select"
class="component-preview-size-select" data-component-debugger-target="size-select"
data-component-debugger-target="size-select" data-action="change->component-debugger#handleWidthDropdown"
data-action="change->component-debugger#handleWidthDropdown" >
> {this.previewSizes.map((size) => (
{this.previewSizes.map((size) => ( <option
<option value={size}
value={size} selected={size === (state.preview_size || "800")}
selected={size === (state.preview_size || "800")} >
> {`${size} px`}
{`${size} px`} </option>
</option> ))}
))} </select>
</select> <noscript>{this.makeActionButton(state, "change_size")}</noscript>
<noscript> </div>
{this.makeActionButton(state, "change_size")} <div class="jdd-container">
</noscript>
</legend>
{render( {render(
registry, registry,
documentContainerFromParsed(state.components), documentContainerFromParsed(state.components),
makeJDDContext(ctx) makeJDDContext(ctx)
)} )}
</fieldset> </div>
{
/* HTML */ `<script>
(function () {
const gutter = document.querySelector(".resize-gutter");
})();
</script>`
}
</div> </div>
</div> </div>
); );

View File

@ -0,0 +1,23 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-assignment */
import { Controller } from "stimulus";
import TurndownService from "turndown";
export default class PasteToMarkdown extends Controller<HTMLTextAreaElement> {
connect() {
this.element.addEventListener("paste", (event) => {
if (event.clipboardData.types.includes("text/html")) {
const turndownService = new TurndownService({
headingStyle: "atx",
preformattedCode: true,
} as any);
event.preventDefault();
const html = (event.clipboardData.getData("text/html") as string)
.replaceAll("\n", " ")
// to get rid of some of the style metadata from libreoffice
.replace(/^<!doctype.*<body[^>]*>/i, "");
document.execCommand("insertText", false, turndownService.turndown(html));
return null;
}
});
}
}

View File

@ -0,0 +1,25 @@
/* eslint-disable @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-assignment */
import { Controller } from "stimulus";
import { throttle } from "throttle-debounce";
export default class SubmitOnInput extends Controller<HTMLTextAreaElement> {
sendValues: () => void;
connect() {
this.sendValues = throttle(
500,
() => {
this.element.closest("form").requestSubmit();
},
{ noTrailing: false }
);
}
makePermanent() {
// this prevents morphing from overwriting the input value with previous half-dane values - https://github.com/hotwired/turbo/issues/1199
this.element.setAttribute("data-turbo-permanent", "");
}
makeNotPermanent() {
this.element.removeAttribute("data-turbo-permanent");
}
}

View File

@ -14,7 +14,7 @@ export const actionName = "Components";
export default new (class JddcomponentDebuggerPage extends JDDPage { export default new (class JddcomponentDebuggerPage extends JDDPage {
renderParameterButtons(state: JDDPageState): Stringifiable { renderParameterButtons(state: JDDPageState): Stringifiable {
const all_components = super.getRegistryCompoments(); const all_components = super.getRegistryComponents();
return ( return (
<div> <div>
<input type="submit" value="Preview" /> <input type="submit" value="Preview" />

View File

@ -19,10 +19,19 @@ application.register("map-with-pins", MapWithPins);
import { default as HorizontalScroller } from "./../back/routes/common/horizontal-scroller/horizontal-scroller.stimulus.js"; import { default as HorizontalScroller } from "./../back/routes/common/horizontal-scroller/horizontal-scroller.stimulus.js";
application.register("horizontal-scroller", HorizontalScroller); application.register("horizontal-scroller", HorizontalScroller);
import { default as AutogrowTextarea } from "./../back/routes/component-preview/autogrow-textarea.stimulus.js";
application.register("autogrow-textarea", AutogrowTextarea);
import { default as ComponentDebugger } from "./../back/routes/component-preview/component-debugger.stimulus.js"; import { default as ComponentDebugger } from "./../back/routes/component-preview/component-debugger.stimulus.js";
application.register("component-debugger", ComponentDebugger); application.register("component-debugger", ComponentDebugger);
import { default as InputImagePreview } from "./../back/routes/component-preview/input-image-preview.stimulus.js"; import { default as InputImagePreview } from "./../back/routes/component-preview/input-image-preview.stimulus.js";
application.register("input-image-preview", InputImagePreview); application.register("input-image-preview", InputImagePreview);
import { default as PasteToMarkdown } from "./../back/routes/component-preview/paste-to-markdown.stimulus.js";
application.register("paste-to-markdown", PasteToMarkdown);
import { default as SubmitOnInput } from "./../back/routes/component-preview/submit-on-input.stimulus.js";
application.register("submit-on-input", SubmitOnInput);
export { Turbo }; export { Turbo };

View File

@ -16,7 +16,8 @@
"allowJs": true, "allowJs": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"sourceMap": true, "sourceMap": true,
"skipLibCheck": true "skipLibCheck": true,
"esModuleInterop": true
}, },
"include": ["./src/back/*", "./src/back/**/*", "./src/back/routes/common/navbar.ts"], "include": ["./src/back/*", "./src/back/**/*", "./src/back/routes/common/navbar.ts"],
"exclude": ["./src/front", "./src/**/*.stimulus.ts"], "exclude": ["./src/front", "./src/**/*.stimulus.ts"],

View File

@ -2,8 +2,9 @@
"compilerOptions": { "compilerOptions": {
"module": "CommonJS", "module": "CommonJS",
"target": "ES6", "target": "ES6",
"lib": ["dom"], "lib": ["dom", "es2021"],
"skipLibCheck": true "skipLibCheck": true,
"esModuleInterop": true
}, },
"include": ["./src/front", "./src/**/*.stimulus.ts"] "include": ["./src/front", "./src/**/*.stimulus.ts"]
} }