diff --git a/docker-compose.yml b/docker-compose.yml index e10506c..48fc368 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,7 +30,7 @@ services: ports: - "127.0.0.1:${PORT:-109}8:7700" volumes: - - ./meili_data:/meili_data + - "${MEILI_VOLUME_PATH:-./meili_data}:/meili_data" environment: MEILI_ENV: development MEILI_MASTER_KEY: ${MEILISEARCH_MASTER_KEY:-qwerty} diff --git a/package-lock.json b/package-lock.json index 63fbc2b..399f02c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,9 @@ "@sealcode/add-to-head": "^1.0.0", "@sealcode/file-manager": "^1.0.2", "@sealcode/jdd": "^0.8.1", - "@sealcode/jdd-editor": "^0.2.4", + "@sealcode/jdd-editor": "^0.2.5", "@sealcode/sealgen": "^0.19.6", + "@sealcode/show-first-row": "^0.1.0", "@sealcode/simplemde": "^1.12.1", "@sealcode/sortable": "^0.0.1", "@sealcode/ts-predicates": "^0.6.2", @@ -37,9 +38,11 @@ "koa-static": "^5.0.0", "locreq": "^3.0.0", "meilisearch": "^0.41.0", + "monaco-editor": "^0.55.1", "mongodb": "^6.17.0", "multiple-scripts-tmux": "^1.0.4", "nodemon": "^3.0.1", + "normalize-diacritics": "^5.0.0", "nyc": "^17.1.0", "object-path": "^0.11.8", "qs": "^6.12.0", @@ -1351,9 +1354,9 @@ } }, "node_modules/@sealcode/jdd": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@sealcode/jdd/-/jdd-0.8.1.tgz", - "integrity": "sha512-UnFS+6q7808kK9NsyagNW/rap9BLkOe60OYM3VeZZd68NvP5SWLQSDvMMrf7IIM50VKDnUx0xf0qLX/9e+IBdA==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@sealcode/jdd/-/jdd-0.8.2.tgz", + "integrity": "sha512-HLaqsEG5Qq+daGEoo0fRpilFreRe8+ZrDnGp4Eqn90xFwz+y7naTZqiStzeN8E6801juIXWgqW5rR6WxlAIKJA==", "dependencies": { "@sealcode/file-manager": "^1.0.2", "@sealcode/ts-predicates": "^0.5.3", @@ -1374,12 +1377,13 @@ } }, "node_modules/@sealcode/jdd-editor": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@sealcode/jdd-editor/-/jdd-editor-0.2.4.tgz", - "integrity": "sha512-Fwp/kb3ViRR+XawMUI1sc9LAOzyjVevHa4MFI4xTzZDvQZCPyaDUoPe20yCoF5BKNqVw4GbkAXG4idJfJ0shyA==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@sealcode/jdd-editor/-/jdd-editor-0.2.5.tgz", + "integrity": "sha512-/1/7pkdV4e2APO77SIc/g2A3mK14JFHDPGtzDigOVGVW0gUW9qPJv76Ov9nVaFapNDu5wa2aMMUxbkweFJB00g==", "dependencies": { "@koa/router": "^13.1.0", - "@sealcode/jdd": "^0.8.1", + "@sealcode/jdd": "^0.8.2", + "@sealcode/monaco-wrapper": "^0.0.3", "@sealcode/sealcodemirror": "^5.70.0-beta5", "@sealcode/sealgen": "^0.19.6", "@types/object-path": "^0.11.4", @@ -1426,6 +1430,22 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "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/monaco-wrapper/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/sealcodemirror": { "version": "5.70.0-beta5", "resolved": "https://registry.npmjs.org/@sealcode/sealcodemirror/-/sealcodemirror-5.70.0-beta5.tgz", @@ -1479,6 +1499,14 @@ "resolved": "https://registry.npmjs.org/@sealcode/ts-predicates/-/ts-predicates-0.4.3.tgz", "integrity": "sha512-UNSEacu7Ye8q0N8AJCJy37oJvv3w2OXKGkUnP7xO5lOY9DQviDPRDQhVaZdJ3/xMzXLm4UE3389ihctrPaov/A==" }, + "node_modules/@sealcode/show-first-row": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sealcode/show-first-row/-/show-first-row-0.1.0.tgz", + "integrity": "sha512-dI/uliwVAloa1ViPwu9njD8jcH/UorrjyAurxaS1ioYFfvXA6mWDkmh/iuiELP4zSh5VcO3AjIPRNyvXQaoO4Q==", + "dependencies": { + "tempstream": "^0.4.6" + } + }, "node_modules/@sealcode/simplemde": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@sealcode/simplemde/-/simplemde-1.12.1.tgz", @@ -1995,6 +2023,12 @@ "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-5.0.2.tgz", "integrity": "sha512-pDzSNulqooSKvSNcksnV72nk8p7gRqN8As71Sp28nov1IgmPKWbOEIwAWvBME5pPTtaXJAvG3O4oc76HlQ4kqQ==" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "optional": true + }, "node_modules/@types/turndown": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz", @@ -4399,6 +4433,14 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/domutils": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", @@ -9450,6 +9492,26 @@ "node": ">=10" } }, + "node_modules/monaco-editor": { + "version": "0.55.1", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", + "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", + "dependencies": { + "dompurify": "3.2.7", + "marked": "14.0.0" + } + }, + "node_modules/monaco-editor/node_modules/marked": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", + "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/mongodb": { "version": "6.17.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz", @@ -9868,6 +9930,22 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/normalize-diacritics": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-diacritics/-/normalize-diacritics-5.0.0.tgz", + "integrity": "sha512-t6czCJOpbAtckN1wCC2qPWnO3GQvNANb9bcUNbiOLEqojVuP31+ELIs5KhEG8jyz0TH7iD9BWxWz8O3ic2/rMQ==", + "hasInstallScript": true, + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 14.x", + "npm": ">= 6.x" + }, + "funding": { + "url": "https://github.com/motss/lit-ntml?sponsor=1" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -13977,6 +14055,11 @@ "node": ">=0.3.1" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "node_modules/tsscmp": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", @@ -15884,9 +15967,9 @@ } }, "@sealcode/jdd": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@sealcode/jdd/-/jdd-0.8.1.tgz", - "integrity": "sha512-UnFS+6q7808kK9NsyagNW/rap9BLkOe60OYM3VeZZd68NvP5SWLQSDvMMrf7IIM50VKDnUx0xf0qLX/9e+IBdA==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@sealcode/jdd/-/jdd-0.8.2.tgz", + "integrity": "sha512-HLaqsEG5Qq+daGEoo0fRpilFreRe8+ZrDnGp4Eqn90xFwz+y7naTZqiStzeN8E6801juIXWgqW5rR6WxlAIKJA==", "requires": { "@sealcode/file-manager": "^1.0.2", "@sealcode/ts-predicates": "^0.5.3", @@ -15916,12 +15999,13 @@ } }, "@sealcode/jdd-editor": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@sealcode/jdd-editor/-/jdd-editor-0.2.4.tgz", - "integrity": "sha512-Fwp/kb3ViRR+XawMUI1sc9LAOzyjVevHa4MFI4xTzZDvQZCPyaDUoPe20yCoF5BKNqVw4GbkAXG4idJfJ0shyA==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@sealcode/jdd-editor/-/jdd-editor-0.2.5.tgz", + "integrity": "sha512-/1/7pkdV4e2APO77SIc/g2A3mK14JFHDPGtzDigOVGVW0gUW9qPJv76Ov9nVaFapNDu5wa2aMMUxbkweFJB00g==", "requires": { "@koa/router": "^13.1.0", - "@sealcode/jdd": "^0.8.1", + "@sealcode/jdd": "^0.8.2", + "@sealcode/monaco-wrapper": "^0.0.3", "@sealcode/sealcodemirror": "^5.70.0-beta5", "@sealcode/sealgen": "^0.19.6", "@types/object-path": "^0.11.4", @@ -15948,6 +16032,24 @@ } } }, + "@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==", + "requires": { + "@sealcode/add-to-head": "^1.0.0", + "monaco-editor": "^0.52.2", + "stimulus": "^3.2.2", + "throttle-debounce": "^5.0.2" + }, + "dependencies": { + "monaco-editor": { + "version": "0.52.2", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", + "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==" + } + } + }, "@sealcode/sealcodemirror": { "version": "5.70.0-beta5", "resolved": "https://registry.npmjs.org/@sealcode/sealcodemirror/-/sealcodemirror-5.70.0-beta5.tgz", @@ -15994,6 +16096,14 @@ } } }, + "@sealcode/show-first-row": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sealcode/show-first-row/-/show-first-row-0.1.0.tgz", + "integrity": "sha512-dI/uliwVAloa1ViPwu9njD8jcH/UorrjyAurxaS1ioYFfvXA6mWDkmh/iuiELP4zSh5VcO3AjIPRNyvXQaoO4Q==", + "requires": { + "tempstream": "^0.4.6" + } + }, "@sealcode/simplemde": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@sealcode/simplemde/-/simplemde-1.12.1.tgz", @@ -16489,6 +16599,12 @@ "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-5.0.2.tgz", "integrity": "sha512-pDzSNulqooSKvSNcksnV72nk8p7gRqN8As71Sp28nov1IgmPKWbOEIwAWvBME5pPTtaXJAvG3O4oc76HlQ4kqQ==" }, + "@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "optional": true + }, "@types/turndown": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz", @@ -18224,6 +18340,14 @@ "domelementtype": "^2.3.0" } }, + "dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "requires": { + "@types/trusted-types": "^2.0.7" + } + }, "domutils": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", @@ -22025,6 +22149,22 @@ } } }, + "monaco-editor": { + "version": "0.55.1", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", + "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", + "requires": { + "dompurify": "3.2.7", + "marked": "14.0.0" + }, + "dependencies": { + "marked": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", + "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==" + } + } + }, "mongodb": { "version": "6.17.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz", @@ -22321,6 +22461,14 @@ "abbrev": "^2.0.0" } }, + "normalize-diacritics": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-diacritics/-/normalize-diacritics-5.0.0.tgz", + "integrity": "sha512-t6czCJOpbAtckN1wCC2qPWnO3GQvNANb9bcUNbiOLEqojVuP31+ELIs5KhEG8jyz0TH7iD9BWxWz8O3ic2/rMQ==", + "requires": { + "tslib": "^2.4.0" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -25282,6 +25430,11 @@ } } }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "tsscmp": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", diff --git a/package.json b/package.json index 86757a0..c245433 100644 --- a/package.json +++ b/package.json @@ -41,14 +41,20 @@ "sealgen": { "styleDirs": [ "node_modules/@sealcode/jdd-editor/assets", - "node_modules/@sealcode/sortable/src/stimulus-styles" + "node_modules/@sealcode/sortable/src/stimulus-styles", + "node_modules/@sealcode/show-first-row/src/styles" ], "controllerDirs": [ "node_modules/@sealcode/jdd-editor/src/controllers", "node_modules/@sealcode/sortable/src/controllers", - "node_modules/@sealcode/sealgen/src/controllers" + "node_modules/@sealcode/sealgen/src/controllers", + "node_modules/@sealcode/monaco-wrapper/src/controllers" ], "copyToPublic": [ + { + "from": "node_modules/monaco-editor", + "to": "dist/monaco-editor" + }, { "from": "node_modules/@sealcode/jdd-editor/assets/icons", "to": "dist/jdd-page/icons" @@ -88,8 +94,9 @@ "@sealcode/add-to-head": "^1.0.0", "@sealcode/file-manager": "^1.0.2", "@sealcode/jdd": "^0.8.1", - "@sealcode/jdd-editor": "^0.2.4", + "@sealcode/jdd-editor": "^0.2.5", "@sealcode/sealgen": "^0.19.6", + "@sealcode/show-first-row": "^0.1.0", "@sealcode/simplemde": "^1.12.1", "@sealcode/sortable": "^0.0.1", "@sealcode/ts-predicates": "^0.6.2", @@ -110,9 +117,11 @@ "koa-static": "^5.0.0", "locreq": "^3.0.0", "meilisearch": "^0.41.0", + "monaco-editor": "^0.55.1", "mongodb": "^6.17.0", "multiple-scripts-tmux": "^1.0.4", "nodemon": "^3.0.1", + "normalize-diacritics": "^5.0.0", "nyc": "^17.1.0", "object-path": "^0.11.8", "qs": "^6.12.0", diff --git a/src/back/jdd-components/autoscrolling-images/autoscrolling-images.css b/src/back/jdd-components/autoscrolling-images/autoscrolling-images.css index 272c3de..99e266f 100644 --- a/src/back/jdd-components/autoscrolling-images/autoscrolling-images.css +++ b/src/back/jdd-components/autoscrolling-images/autoscrolling-images.css @@ -175,13 +175,7 @@ } } -@container (width < 1115px) { - .autoscrolling-images__arrow-carousel-container { - right: 0; - } -} - -@container (width < 800px) { +.autoscrolling-images.endless { .autoscrolling-images__arrow-carousel-container { display: none; } @@ -220,4 +214,68 @@ .autoscrolling-images__carousel-page--looping-tail { display: flex; } + + @container (width > 800px) { + .autoscrolling-images__carousel-page { + display: grid !important; + grid-template-rows: repeat(2, auto); + grid-auto-flow: column; + gap: 16px; + } + + .autoscrolling-images__img-wrapper:last-child:nth-child(odd) { + grid-row: 1 / span 2; /* take both rows */ + align-self: center; + justify-self: center; + } + } +} + +.autoscrolling-images:not(.endless) { + @container (width < 1115px) { + .autoscrolling-images__arrow-carousel-container { + right: 0; + } + } + + @container (width < 800px) { + .autoscrolling-images__arrow-carousel-container { + display: none; + } + + .autoscrolling-images__dots-container { + display: none; + } + + .autoscrolling-images__carousel { + max-width: none; + width: max-content; + animation-name: autoscrolling-images-infinite-scroll; + animation-duration: var(--animation-length); + animation-iteration-count: infinite; + animation-timing-function: linear; + } + + .autoscrolling-images__carousel-container { + margin: 0; + max-width: calc(100cqw - 20px); + } + + .autoscrolling-images__carousel-page { + display: flex; + flex: none !important; + margin: 0; + flex-wrap: nowrap !important; + max-width: none; + } + + .autoscrolling-images__img-wrapper { + min-width: 288px; + } + + .autoscrolling-images__carousel-page--looping-head, + .autoscrolling-images__carousel-page--looping-tail { + display: flex; + } + } } diff --git a/src/back/jdd-components/autoscrolling-images/autoscrolling-images.jdd.tsx b/src/back/jdd-components/autoscrolling-images/autoscrolling-images.jdd.tsx index 2030490..9af8a78 100644 --- a/src/back/jdd-components/autoscrolling-images/autoscrolling-images.jdd.tsx +++ b/src/back/jdd-components/autoscrolling-images/autoscrolling-images.jdd.tsx @@ -32,6 +32,7 @@ const component_arguments = { ]), speed: new ComponentArguments.ShortText().setExampleValues(["5"]), desktop_interval: new ComponentArguments.ShortText().setExampleValues(["5"]), + endless: new ComponentArguments.Enum(["true", "false"]), images, } as const; @@ -121,7 +122,15 @@ export class AutoscrollingImages extends Component { } toHTML({ - args: { title, imagesPerPage, images, speed, desktop_interval, background_mode }, + args: { + title, + imagesPerPage, + images, + speed, + desktop_interval, + background_mode, + endless, + }, jdd_context: { render_image }, classes, index, @@ -141,6 +150,7 @@ export class AutoscrollingImages extends Component { class={[ "autoscrolling-images", `autoscrolling-images--mode-${background_mode}`, + endless == "true" ? "endless" : "", ...classes, ]} style={`--jdd-index: ${index}`} diff --git a/src/back/jdd-components/ordered-blocks/ordered-blocks.css b/src/back/jdd-components/ordered-blocks/ordered-blocks.css new file mode 100644 index 0000000..6e82ad3 --- /dev/null +++ b/src/back/jdd-components/ordered-blocks/ordered-blocks.css @@ -0,0 +1,184 @@ +.ordered-blocks { + padding-bottom: 96px; + position: relative; +} + +.ordered-blocks ol { + list-style-type: none; + padding: 0; +} + +.ordered-blocks__body { + margin: 0 auto; + position: relative; + z-index: 2; +} + +.ordered-blocks__article-list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(36ch, 1fr)); + padding: 0 24px; +} + +.ordered-blocks__buttons { + display: flex; + align-items: center; + justify-content: center; + gap: 24px; +} + +.ordered-blocks__button-wrapper { + text-align: center; +} + +@container (max-width: 400px) { + .ordered-blocks__article-list { + display: block; + max-width: 100%; + padding: 0; + margin: auto; + } + +} + +.ordered-blocks .article-list-item { + background-color: var(--main); + padding: 0 24px; + padding-top: 24px; +} + +.ordered-blocks .article-list-item__number { + display: block; + font-family: var(--font-sans-serif); + font-feature-settings: "tnum"; + font-weight: 800; + font-size: 24px; + line-height: 24px; + padding-bottom: calc(12px - 1px); + border-bottom: 1px solid #000000; + margin-bottom: 12px; +} + +.ordered-blocks .article-list-item__image { + padding-bottom: calc(24px - 1px); + border-bottom: 1px solid #000000; + margin-bottom: 12px; +} + +.ordered-blocks .article-list-item__image img { + height: 60px; +} + +.ordered-blocks .article-list-item--above .article-list-item__image { + border: none; + padding: 0; + margin: 0; +} + +.ordered-blocks .article-list-item h3 { + font-size: 20px; + line-height: 24px; + font-family: var(--font-slab-serif); + font-weight: 400; +} + +.ordered-blocks .article-list-item p { + font-family: var(--font-sans-serif); + font-size: 15px; + padding-top: 24px; +} + +.ordered-blocks .article-list-item--above .header { + display: flex; + flex-flow: row; + padding-bottom: calc(9.6px - 1px); + border-bottom: 1px solid #000000; + margin-bottom: 12px; + align-items: center; +} + +.ordered-blocks .article-list-item--purple .article-list-item__image { + border-color: hsl(287.3, 13.1%, 50.8%); +} + +.ordered-blocks .article-list-item--above .article-list-item__number { + padding-bottom: 0; + border-bottom: none; + margin-bottom: 0; +} + +.ordered-blocks .article-list-item--above p { + padding-top: 0; +} + +@media (max-width: 460px) { + .ordered-blocks .article-list-item--above .article-list-item__number { + display: block; + padding-left: 0; + border-bottom: 1px solid #000000; + padding-bottom: calc(12px - 1px); + margin-bottom: 12px; + } + + .ordered-blocks .article-list-item--above .header { + display: block; + border: none; + } + + .ordered-blocks .article-list-item--above .header h3 { + margin-left: 0; + padding-top: 0; + } +} + +.ordered-blocks .article-list-item--purple .header { + border-color: hsl(287.3, 13.1%, 50.8%); +} + +.ordered-blocks .article-list-item--purple .article-list-item__number { + color: var(--main-bg-text); + border-color: hsl(287.3, 13.1%, 50.8%); +} + +.ordered-blocks .article-list-item--purple h3 { + color: var(--main-bg-text); +} + +.ordered-blocks .article-list-item--purple p { + color: hsl(286.4, 15.5%, 83.2%); +} + +.ordered-blocks .article-list-item--wild-sand { + background: none; +} + +.ordered-blocks .article-list-item--wild-sand .header { + border-color: var(--alto-gray); +} + +.ordered-blocks .article-list-item--wild-sand .article-list-item__number { + color: var(--secondary-dark-01); + border-color: var(--alto-gray); +} + +.ordered-blocks .article-list-item--wild-sand .article-list-item__image { + border-color: var(--alto-gray); +} + +.ordered-blocks .article-list-item--wild-sand h3 { + color: var(--wild-sand-bg-text); +} + +.ordered-blocks .article-list-item--above .header h3 { + padding-top: 2.4px; + margin-left: 12px; +} + +.ordered-blocks .article-list-item--wild-sand p { + color: var(--dove-gray); +} + +.ordered-blocks .article-list-item .button { + margin: 6px auto; + display: block; +} \ No newline at end of file diff --git a/src/back/jdd-components/ordered-blocks/ordered-blocks.jdd.tsx b/src/back/jdd-components/ordered-blocks/ordered-blocks.jdd.tsx new file mode 100644 index 0000000..c9cb49a --- /dev/null +++ b/src/back/jdd-components/ordered-blocks/ordered-blocks.jdd.tsx @@ -0,0 +1,96 @@ +import type { FlatTemplatable } from "tempstream"; +import { TempstreamJSX } from "tempstream"; +import type { ComponentToHTMLArgs } from "@sealcode/jdd"; +import { Component, ComponentArguments } from "@sealcode/jdd"; +import { button } from "../../elements/button.js"; + +const component_arguments = { + list_items: new ComponentArguments.List( + new ComponentArguments.Structured({ + name: new ComponentArguments.ShortText(), + description: new ComponentArguments.Markdown(), + btn_config: new ComponentArguments.Structured({ + text: new ComponentArguments.ShortText(), + href: new ComponentArguments.ShortText(), + }), + }) + ), + btn_config: new ComponentArguments.Structured({ + text: new ComponentArguments.ShortText(), + href: new ComponentArguments.ShortText(), + }), + btn2_config: new ComponentArguments.Structured({ + text: new ComponentArguments.ShortText(), + href: new ComponentArguments.ShortText(), + }), + header_mode: new ComponentArguments.Enum(["below", "above"]), + starting_index: new ComponentArguments.ShortText().setExampleValues(["1"]), +} as const; + +export class OrderedBlocks extends Component { + getArguments() { + return component_arguments; + } + + toHTML({ + args: { list_items, btn2_config, btn_config, header_mode, starting_index }, + jdd_context: { render_markdown, language }, + classes, + }: ComponentToHTMLArgs): FlatTemplatable { + return ( +
+
+
    + {list_items.map(({ name, description, btn_config }, index) => { + const number = String( + index + parseInt(starting_index) + ).padStart(2, "0"); + + return ( +
  1. +
    + + {number} + +

    {name || ""}

    +
    + {render_markdown(language, description)} + {btn_config.text && btn_config.href + ? button({ + text: btn_config.text, + href: btn_config.href, + variant: "accent1", + }) + : null} +
  2. + ); + })} +
+
+ {btn_config.text && btn_config.href ? ( +
+ {button({ + text: btn_config.text, + href: btn_config.href, + variant: "accent1", + })} +
+ ) : null} + {btn2_config.text && btn2_config.href ? ( +
+ {button({ + text: btn2_config.text, + href: btn2_config.href, + variant: "accent1", + })} +
+ ) : null} +
+
+
+ ); + } +} diff --git a/src/back/routes/common/show-first-row/show-first-row.css b/src/back/routes/common/show-first-row/show-first-row.css deleted file mode 100644 index e7ede82..0000000 --- a/src/back/routes/common/show-first-row/show-first-row.css +++ /dev/null @@ -1,49 +0,0 @@ -.show-first-row { - container-type: inline-size; - text-align: center; - width: 100%; - - .show-first-row__items { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(var(--min-column-width), 1fr)); - justify-content: center; - gap: var(--column-gap); - transition: row-gap 200ms, overflow 200ms; - overflow: hidden; - - .show-first-row__checkbox:not(:checked) + & { - row-gap: 0; - } - - & > * { - align-self: center; - justify-self: center; - } - } - - .show-first-row__default-button { - background: blue; - color: white; - height: 40px; - display: inline-block; - padding: 0 20px; - line-height: 40px; - cursor: pointer; - margin: 0 auto; - margin-top: 20px; - } - - .show-first-row__show-less-button { - display: none; - } - - &:has(input:checked) { - .show-first-row__show-less-button { - display: block; - } - - .show-first-row__show-more-button { - display: none; - } - } -} diff --git a/src/back/routes/common/show-first-row/show-first-row.tsx b/src/back/routes/common/show-first-row/show-first-row.tsx deleted file mode 100644 index 0a9ad20..0000000 --- a/src/back/routes/common/show-first-row/show-first-row.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { TempstreamJSX } from "tempstream"; -import type { FlatTemplatable } from "tempstream"; - -const showFirstRowIds = (function* () { - let i = 0; - while (true) { - yield i++; - if (i == 999999999) { - i = 0; - } - } -})(); - -function min_container_width(columns: number, column_width: number, gap: number) { - return columns * column_width + (columns - 1) * gap; -} - -export async function showFirstRow({ - items, - min_column_width_px = 250, - column_gap_px = 10, - row_gap_px = column_gap_px, - make_show_more_button = () => ( - Show more - ), - make_show_less_button = () => ( - Show less - ), - add_button_to_content = (content, show_more_button, show_less_button) => ( - <> - {content} - {show_more_button} - {show_less_button} - - ), - classes = [], - how_many_rows = () => 1, -}: { - items: FlatTemplatable[]; - min_column_width_px?: number; - make_show_more_button?: () => JSX.Element; - make_show_less_button?: () => JSX.Element; - column_gap_px?: number; - row_gap_px?: number; - add_button_to_content?: ( - content: JSX.Element, - show_more_button: JSX.Element, - show_less_button: JSX.Element - ) => JSX.Element; - classes?: classNames.ArgumentArray; - how_many_rows?: (columns_count: number) => number; -}): Promise { - const id = showFirstRowIds.next().value; - const checkbox_id = "show-first-row__checkbox--" + id; - return ( -
- {[1, 2, 3, 4, 5, 6, 7, 8, 9] - .reverse() - .map((columns) => { - const rows = how_many_rows(columns); - return /* HTML */ ``; - }) - .join("\n")} - - {add_button_to_content( -
{items}
, - , - - )} -
- ); -} diff --git a/src/back/routes/common/tabs/tabs.tsx b/src/back/routes/common/tabs/tabs.tsx index bbecad2..0f6890d 100644 --- a/src/back/routes/common/tabs/tabs.tsx +++ b/src/back/routes/common/tabs/tabs.tsx @@ -1,5 +1,6 @@ import type { FlatTemplatable } from "tempstream"; import { TempstreamJSX } from "tempstream"; +import { normalizeSync as normalize } from "normalize-diacritics"; const ids = (function* () { let i = 0; @@ -11,6 +12,14 @@ const ids = (function* () { } })(); +const normalizeId = (id: string) => { + const normalized = normalize(id); + const hyphened = normalized.replace(/[^\w-]/g, "-"); + const squashedHyphens = hyphened.replace(/-+/g, "-"); + + return squashedHyphens; +}; + export function tabs({ tabs, default_tab, @@ -29,18 +38,24 @@ export function tabs({ active_navbar_tab_style?: string; }) { const tab_section_id = ids.next().value; + + const tabsNormalizedIds = tabs.map((tab) => ({ + ...tab, + id: normalizeId(tab.id), + })); + return (
{tab_bar || ( )} - {tabs.map(({ id, content }) => { + {tabsNormalizedIds.map(({ id, content }) => { const tab_id = `tabs__${tab_section_id}__tab__${id}`; return (
diff --git a/src/back/routes/demos/show-first-row.css b/src/back/routes/demos/show-first-row.css new file mode 100644 index 0000000..506eba4 --- /dev/null +++ b/src/back/routes/demos/show-first-row.css @@ -0,0 +1,4 @@ +.show-first-row .show-first-row__default-button { + background-color: var(--color-brand-accent); + color: var(--color-brand-text-on-accent); +} diff --git a/src/back/routes/demos/show-first-row.page.tsx b/src/back/routes/demos/show-first-row.page.tsx index 9b6e085..299d9d4 100644 --- a/src/back/routes/demos/show-first-row.page.tsx +++ b/src/back/routes/demos/show-first-row.page.tsx @@ -2,14 +2,14 @@ import type { Context } from "koa"; import { TempstreamJSX } from "tempstream"; import { Page } from "@sealcode/sealgen"; import html from "../../html.js"; -import { showFirstRow } from "../common/show-first-row/show-first-row.js"; +import { showFirstRow } from "@sealcode/show-first-row"; export const actionName = "ShowFirstRowDemo"; function makeBoxStyle() { return `width: 100%; max-width: 200px; height: ${ 100 + Math.random() * 100 - }px; background-color: lime; text-align: center; color: #0000009e; font-size: 40px; padding-top: 20px; box-sizing: border-box;`; + }px; background-color: var(--color-brand-accent2); text-align: center; color: #0000009e; font-size: 40px; padding-top: 20px; box-sizing: border-box;`; } export default new (class ShowFirstRowDemoPage extends Page { diff --git a/src/back/routes/index.ts b/src/back/routes/index.ts index 1135d9e..3642812 100644 --- a/src/back/routes/index.ts +++ b/src/back/routes/index.ts @@ -8,6 +8,7 @@ import { imageRouter, RESPONSIVE_IMAGES_URL_PATH } from "../image-router.js"; import { customUrlView } from "./middlewares/customUrlView.js"; import mountAutoRoutes from "./routes.js"; import _locreq from "locreq"; +import sitemapPage from "../sitemap.xml.js"; const locreq = _locreq(new URL("./", import.meta.url).pathname); export const mainRouter = (app: TheApp, koa_app: Koa, router: Router): void => { @@ -41,6 +42,11 @@ export const mainRouter = (app: TheApp, koa_app: Koa, router: Router): void => { ctx.body = { status: ctx.$app.status, started_at }; }); + router.get("/sitemap.xml", async (ctx) => { + ctx.set("Content-Type", "application/xml"); + ctx.body = await sitemapPage.render(ctx); + }); + router.use(RESPONSIVE_IMAGES_URL_PATH, imageRouter.getRoutes()); mountAutoRoutes(router); diff --git a/src/back/routes/login.form.ts b/src/back/routes/login.form.ts index eb3c9cf..751d775 100644 --- a/src/back/routes/login.form.ts +++ b/src/back/routes/login.form.ts @@ -69,6 +69,7 @@ export default new (class LoginForm extends Form { ctx, title: "Form", description: "", + css_clumps: ["admin-forms"], body: super.render(ctx, data, show_field_errors) as Promise, }); } diff --git a/src/back/routes/signIn.form.ts b/src/back/routes/signIn.form.ts index afcec37..ef627fe 100644 --- a/src/back/routes/signIn.form.ts +++ b/src/back/routes/signIn.form.ts @@ -117,6 +117,7 @@ export default new (class SignInForm extends Form { ctx, title: "SignIn", description: "", + css_clumps: ["admin-forms"], body: tempstream`${await super.render(ctx, data, show_field_errors)}`, }); } diff --git a/src/back/sitemap.xml.tsx b/src/back/sitemap.xml.tsx new file mode 100644 index 0000000..482982c --- /dev/null +++ b/src/back/sitemap.xml.tsx @@ -0,0 +1,29 @@ +import type { Context } from "koa"; +import { Page } from "@sealcode/sealgen"; + +export const actionName = "Sitemap"; + +async function generateSitemap(ctx: Context) { + const pages = await ctx.$app.collections.pages.list(ctx.$context).fetch(); + + let xml = `\n`; + xml += `\n`; + + for (const page of pages.items) { + xml += ` \n ${ctx.origin.replace(/\/$/, "")}${page.get("url")}\n \n`; + } + + xml += `\n`; + return xml; +} + +export default new (class SitemapPage extends Page { + async canAccess() { + return { canAccess: true, message: "" }; + } + + async render(ctx: Context) { + ctx.set("Content-Type", "application/xml"); + return generateSitemap(ctx); + } +})(); diff --git a/src/bg.png b/src/bg.png deleted file mode 100644 index 841eb68..0000000 Binary files a/src/bg.png and /dev/null differ diff --git a/src/front/controllers.ts b/src/front/controllers.ts index 687c92f..11519b8 100644 --- a/src/front/controllers.ts +++ b/src/front/controllers.ts @@ -49,6 +49,9 @@ application.register("submit-on-input", SubmitOnInput); import { default as Toast } from "./../../node_modules/@sealcode/jdd-editor/src/controllers/toast.stimulus.js"; 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); diff --git a/tests/sitemap.test.ts b/tests/sitemap.test.ts new file mode 100644 index 0000000..114ca72 --- /dev/null +++ b/tests/sitemap.test.ts @@ -0,0 +1,54 @@ +import * as assert from "node:assert"; +import { test, expect } from "./backend-fixture"; + +test("sitemap returns expected XML for empty DB", async ({ backend, request }) => { + const response = await request.get(`${backend.url}/sitemap.xml`); + assert.strictEqual(response.status(), 200); + + const actual = await response.text(); + const expected = ` + + + ${backend.url}/ + + +`; + assert.strictEqual(actual, expected); +}); + +test("sitemap returns expected XML after creating a page via UI", async ({ + page, + backend, + request, +}) => { + await page.goto(backend.url); + await page.getByRole("link", { name: "Go to Admin" }).click(); + await page.getByPlaceholder("text").fill("admin"); + await page.getByPlaceholder("text").press("Tab"); + await page.getByPlaceholder("password").fill("adminadmin"); + await page.getByRole("button", { name: "Wyślij" }).click(); + + await page.getByRole("link", { name: "Edit Pages" }).click(); + await page.getByRole("link", { name: "Create" }).click(); + await page.getByLabel("url").fill("/my-page/"); + await page.getByLabel("title").fill("My Page"); + await page.getByLabel("heading").fill("My Heading"); + await page.getByLabel("description").fill("My Description"); + await page.getByRole("button", { name: "Wyślij" }).click(); + + const response = await request.get(`${backend.url}/sitemap.xml`); + assert.strictEqual(response.status(), 200); + + const actual = await response.text(); + const expected = ` + + + ${backend.url}/ + + + ${backend.url}/my-page/ + + +`; + assert.strictEqual(actual, expected); +});