diff --git a/index.html b/index.html index aa6c9f5..988dcd6 100644 --- a/index.html +++ b/index.html @@ -7,16 +7,77 @@ content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0" /> Astro Decap Collection Demo + + - - -
-
+
+

Astro Decap Collection Demo

+

+ This is a simple demo generator to transform Decap collections to Astro compatible Zod + schemata.
+ For more information, please visit the + GitHub repository. +

+ + +
+
+
+ Decap config +
+
+ +
+ +
+
+ Parsed config collections +
+
+
+ Zod schemata +
+
+
diff --git a/package-lock.json b/package-lock.json index 43e1820..77ff5c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,10 +28,12 @@ "eslint-plugin-prettier": "5.2.1", "eslint-plugin-simple-import-sort": "12.1.1", "eslint-plugin-unused-imports": "4.1.4", + "highlight.js": "11.10.0", "jest": "29.7.0", "jest-junit": "16.0.0", "npm-run-all": "4.1.5", "prettier": "3.3.3", + "simple-icons": "13.14.1", "ts-jest": "29.2.5", "ts-node": "10.9.2", "typescript": "5.6.3", @@ -8131,6 +8133,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/highlight.js": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.10.0.tgz", + "integrity": "sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/history": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", @@ -15270,6 +15282,20 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-icons": { + "version": "13.14.1", + "resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-13.14.1.tgz", + "integrity": "sha512-Syxm5ZpLgfePbZ8TXoOgiazaUqutE48eXhY8Mix2vK0uPji2rl+A2XG/sEyPBwIVj0tZeXuWolyTtMURASswTg==", + "dev": true, + "license": "CC0-1.0", + "engines": { + "node": ">=0.12.18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/simple-icons" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", diff --git a/package.json b/package.json index 0a5b663..f3eb466 100644 --- a/package.json +++ b/package.json @@ -55,10 +55,12 @@ "eslint-plugin-prettier": "5.2.1", "eslint-plugin-simple-import-sort": "12.1.1", "eslint-plugin-unused-imports": "4.1.4", + "highlight.js": "11.10.0", "jest": "29.7.0", "jest-junit": "16.0.0", "npm-run-all": "4.1.5", "prettier": "3.3.3", + "simple-icons": "13.14.1", "ts-jest": "29.2.5", "ts-node": "10.9.2", "typescript": "5.6.3", diff --git a/src/demo/demo.css b/src/demo/demo.css index e295dda..851e629 100644 --- a/src/demo/demo.css +++ b/src/demo/demo.css @@ -1,24 +1,128 @@ +html { + font-family: system-ui; + font-size: 12px; +} + +body, +h1, +p, +pre, +textarea { + margin: 0; +} + body { display: flex; - flex-flow: row nowrap; + flex-flow: column nowrap; box-sizing: border-box; height: 100svh; - margin: 0; padding: 10px; gap: 10px; } +header { + display: grid; + grid-template: + 'title nav' + 'description .' / 1fr; + gap: 10px; + + h1 { + grid-area: title; + } + + p { + grid-area: description; + } + + nav { + grid-area: nav; + display: flex; + flex-flow: row nowrap; + gap: 5px; + + img { + height: 1.5em; + } + } +} + +main { + flex: 1 1 0; + display: flex; + flex-flow: row nowrap; + gap: 10px; + + @media (orientation: portrait) { + flex-direction: column; + } +} + +h1, +p { + margin: 0; +} + +h1 { + font-size: 1.5em; +} + +a:visited { + color: currentcolor; +} + +button, +fieldset { + all: unset; +} + +fieldset { + position: relative; + box-sizing: border-box; + flex: 1 1 0; + + height: 100%; + width: 0; + + @media (orientation: portrait) { + height: 0; + width: 100%; + } +} + +button, +legend, +code::after { + font: 0.8em/1 system-ui; + text-transform: uppercase; +} + button { - position: fixed; - inset: auto auto 20px 20px; + padding: 5px 10px; + border-radius: 3px; + background: #ccc; + cursor: pointer; + + &:hover { + background-color: #dcdcdc; + } +} + +legend { + padding: 0 0 3px; +} + +#input > div, +textarea, +pre { + height: 100%; + width: 100%; } textarea, pre { - flex: 1 1 0; - width: 0; - margin: 0; border: 0; + box-sizing: border-box; resize: none; } @@ -26,7 +130,8 @@ textarea, pre > code { padding: 10px; border: 1px solid #ccc; - border-radius: 5px; + border-radius: 6px; + font: 1em/1.5 monospace; } pre { @@ -34,33 +139,65 @@ pre { flex-flow: column nowrap; gap: 10px; + @media (orientation: portrait) { + flex-direction: row; + } + > code { flex: 1 1 0; - position: relative; display: block; box-sizing: border-box; max-height: 100%; padding: 10px; overflow: hidden; overflow-y: auto; - - &::after { - content: attr(data-label); - position: absolute; - inset: 10px 10px auto auto; - padding: 3px 5px; - border-radius: 3px; - background: #ccc; - } } } -@media (orientation: portrait) { - body { - flex-direction: column; +code { + position: relative; + + &[data-label]::after { + content: attr(data-label); + position: absolute; + inset: 3px 3px auto auto; + padding: 3px 5px; + border-radius: 3px; + background: #ccc; } +} + +#input { + position: relative; pre { - flex-direction: row; + white-space: pre-wrap; + } + + textarea { + position: absolute; + inset: 0; + z-index: 1; + + background: transparent; + border-color: transparent; + color: transparent; + + caret-color: #000; + + &:focus { + outline: 1px solid #ccc; + outline-offset: -2px; + } + } + + nav { + position: absolute; + inset: auto auto 10px 10px; + z-index: 2; + + display: flex; + flex-flow: row nowrap; + gap: 10px; } } diff --git a/src/demo/demo.ts b/src/demo/demo.ts index 6fa8803..3c7d376 100644 --- a/src/demo/demo.ts +++ b/src/demo/demo.ts @@ -1,3 +1,7 @@ +import hljs from 'highlight.js'; +import json from 'highlight.js/lib/languages/json'; +import ts from 'highlight.js/lib/languages/typescript'; +import yaml from 'highlight.js/lib/languages/yaml'; import zod from 'zod'; import { parseConfig } from '../utils/decap.utils.js'; @@ -9,10 +13,17 @@ declare global { global: Window; loadExample(path: string): void; handleInput(event: InputEvent): Promise; - updatePreview(config: string, schemas: Record): void; + handleScroll(event: Event): void; + updatePreview(input: string, config: string, schemas: Record): void; } } +// register highlight languages +hljs.registerLanguage('ts', ts); +hljs.registerLanguage('json', json); +hljs.registerLanguage('yaml', yaml); + +// prevent errors from libs using ancient `global` instead of `globalThis` window.global ||= window; // loads an example from the `examples` folder @@ -23,25 +34,43 @@ window.loadExample = async (path: string) => { input.dispatchEvent(new InputEvent('input')); }; +// handle textarea input event window.handleInput = async event => { - const { value } = event.target as HTMLTextAreaElement; - const { collections } = await parseConfig(value); + const { value: config } = event.target as HTMLTextAreaElement; + const { collections } = (await parseConfig(config)) ?? {}; + if (collections === undefined) { + window.updatePreview(config, '', {}); + return; + } + const schemas = await Promise.all( collections.map(async collection => { const { cptime } = transformCollection(collection, { zod }); return [collection.name, await formatCode(cptime)]; }), ); - window.updatePreview(JSON.stringify(collections, null, 2), Object.fromEntries(schemas)); + window.updatePreview(config, JSON.stringify(collections, null, 2), Object.fromEntries(schemas)); }; -window.updatePreview = (config, schemas) => { - const configPreview = document.querySelector('#config > code'); - configPreview.dataset.label = `${Object.entries(schemas).length}`; - configPreview.innerHTML = config; +window.handleScroll = event => { + const { scrollTop, parentElement } = event.target as HTMLTextAreaElement; + parentElement.firstElementChild.firstElementChild.scrollTop = scrollTop; +}; + +// update the preview code with the config and schemas +window.updatePreview = (input, config, schemas) => { + const inputPreview = document.querySelector('#input code'); + inputPreview.innerHTML = hljs.highlight(input, { language: 'yaml' }).value; + + const configPreview = document.querySelector('#config code'); + configPreview.dataset.label = `count: ${Object.entries(schemas).length}`; + configPreview.innerHTML = hljs.highlight(config, { language: 'json' }).value; - const schemaPreview = document.querySelector('#schema'); + const schemaPreview = document.querySelector('#schemas pre'); schemaPreview.innerHTML = Object.entries(schemas) - .map(([name, schema]) => `${schema}`) + .map( + ([name, schema]) => + `${hljs.highlight(schema, { language: 'ts' }).value}`, + ) .join('\n\n'); };