feat: tag cloud. macro
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
- [Working with posts](#working-with-posts)
|
- [Working with posts](#working-with-posts)
|
||||||
- [Working with pages](#working-with-pages)
|
- [Working with pages](#working-with-pages)
|
||||||
- [Working with media](#working-with-media)
|
- [Working with media](#working-with-media)
|
||||||
|
- [Using macros](#using-macros)
|
||||||
- [Organizing with tags](#organizing-with-tags)
|
- [Organizing with tags](#organizing-with-tags)
|
||||||
- [Importing from WordPress (WXR)](#importing-from-wordpress-wxr)
|
- [Importing from WordPress (WXR)](#importing-from-wordpress-wxr)
|
||||||
- [Using Git (Source Control)](#using-git-source-control)
|
- [Using Git (Source Control)](#using-git-source-control)
|
||||||
@@ -143,6 +144,56 @@ After placing media in content, run a quick preview pass to confirm placement an
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Using macros
|
||||||
|
|
||||||
|
Macros let you insert dynamic content blocks directly inside post/page Markdown by using `[[macro_name ...]]` syntax. bDS expands these macros during preview and generated output using local assets only.
|
||||||
|
|
||||||
|
Use macros when you need reusable rich blocks (for example embedded videos, media galleries, archive grids, or computed tag clouds) without writing raw HTML.
|
||||||
|
|
||||||
|
### Supported macros
|
||||||
|
|
||||||
|
- `[[youtube id="VIDEO_ID" title="Optional title"]]`
|
||||||
|
- Embeds a YouTube video.
|
||||||
|
- `id` is required.
|
||||||
|
- `title` is optional (used for accessibility label).
|
||||||
|
|
||||||
|
- `[[vimeo id="VIDEO_ID" title="Optional title"]]`
|
||||||
|
- Embeds a Vimeo video.
|
||||||
|
- `id` is required.
|
||||||
|
- `title` is optional.
|
||||||
|
|
||||||
|
- `[[gallery columns="3" caption="Optional caption"]]`
|
||||||
|
- Renders a lightbox-enabled gallery using media linked to the current post.
|
||||||
|
- `columns` is optional (`1` to `6`, default `3`).
|
||||||
|
- `caption` is optional.
|
||||||
|
|
||||||
|
- `[[photo_archive year="2025" month="2"]]`
|
||||||
|
- Renders a photo archive grid from media dates.
|
||||||
|
- `year` is optional (when omitted, recent months are shown).
|
||||||
|
- `month` is optional (used with `year` for a single month view).
|
||||||
|
- Legacy alias `[[photo_album ...]]` is also supported.
|
||||||
|
|
||||||
|
- `[[tag_cloud orientation="mixed_diagonal" width="900" height="420"]]`
|
||||||
|
- Builds a word cloud from published tag usage counts.
|
||||||
|
- Word size scales by usage quantity.
|
||||||
|
- Word color scales by quantity from least to most: blue → green → yellow → orange → red.
|
||||||
|
- Clicking a word opens that tag archive route.
|
||||||
|
- `orientation` is optional and supports:
|
||||||
|
- `horizontal` (all words horizontal)
|
||||||
|
- `mixed_hv` (mix of horizontal and vertical)
|
||||||
|
- `mixed_diagonal` (mix of horizontal/vertical/diagonal angles)
|
||||||
|
- `width` and `height` are optional (defaults `900` and `420`).
|
||||||
|
|
||||||
|
### Key takeaways
|
||||||
|
|
||||||
|
- Macros are inserted directly in Markdown and expanded during preview/publish rendering.
|
||||||
|
- Use macro parameters to control behavior without leaving the editor.
|
||||||
|
- `tag_cloud` is data-driven and links directly into tag archive navigation.
|
||||||
|
|
||||||
|
[↑ Back to In this article](#in-this-article)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Organizing with tags
|
## Organizing with tags
|
||||||
|
|
||||||
Tags are your precision taxonomy tool. Over time, even well-managed projects accumulate near-duplicate tags, naming inconsistencies, and labels that no longer serve users. The Tags section exists to keep taxonomy useful and prevent search and filtering quality from degrading.
|
Tags are your precision taxonomy tool. Over time, even well-managed projects accumulate near-duplicate tags, naming inconsistencies, and labels that no longer serve users. The Tags section exists to keep taxonomy useful and prevent search and filtering quality from degrading.
|
||||||
|
|||||||
78
package-lock.json
generated
78
package-lock.json
generated
@@ -25,6 +25,7 @@
|
|||||||
"@picocss/pico": "^2.1.1",
|
"@picocss/pico": "^2.1.1",
|
||||||
"@xmldom/xmldom": "^0.8.11",
|
"@xmldom/xmldom": "^0.8.11",
|
||||||
"chokidar": "^5.0.0",
|
"chokidar": "^5.0.0",
|
||||||
|
"d3-cloud": "^1.2.8",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"drizzle-orm": "^0.45.1",
|
"drizzle-orm": "^0.45.1",
|
||||||
"dropbox": "^10.34.0",
|
"dropbox": "^10.34.0",
|
||||||
@@ -171,6 +172,7 @@
|
|||||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@babel/code-frame": "^7.29.0",
|
||||||
"@babel/generator": "^7.29.0",
|
"@babel/generator": "^7.29.0",
|
||||||
@@ -744,6 +746,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.1.tgz",
|
||||||
"integrity": "sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==",
|
"integrity": "sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.0.0",
|
"@codemirror/state": "^6.0.0",
|
||||||
"@codemirror/view": "^6.23.0",
|
"@codemirror/view": "^6.23.0",
|
||||||
@@ -820,6 +823,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz",
|
||||||
"integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==",
|
"integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@marijn/find-cluster-break": "^1.0.0"
|
"@marijn/find-cluster-break": "^1.0.0"
|
||||||
}
|
}
|
||||||
@@ -841,6 +845,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.13.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.13.tgz",
|
||||||
"integrity": "sha512-QBO8ZsgJLCbI28KdY0/oDy5NQLqOQVZCozBknxc2/7L98V+TVYFHnfaCsnGh1U+alpd2LOkStVwYY7nW2R1xbw==",
|
"integrity": "sha512-QBO8ZsgJLCbI28KdY0/oDy5NQLqOQVZCozBknxc2/7L98V+TVYFHnfaCsnGh1U+alpd2LOkStVwYY7nW2R1xbw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.5.0",
|
"@codemirror/state": "^6.5.0",
|
||||||
"crelt": "^1.0.6",
|
"crelt": "^1.0.6",
|
||||||
@@ -936,6 +941,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.19.0"
|
"node": ">=20.19.0"
|
||||||
},
|
},
|
||||||
@@ -976,6 +982,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.19.0"
|
"node": ">=20.19.0"
|
||||||
}
|
}
|
||||||
@@ -1370,7 +1377,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-dirname": "^0.1.0",
|
"cross-dirname": "^0.1.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
@@ -1392,7 +1398,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.2.0",
|
"graceful-fs": "^4.2.0",
|
||||||
"jsonfile": "^6.0.1",
|
"jsonfile": "^6.0.1",
|
||||||
@@ -1409,7 +1414,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"universalify": "^2.0.0"
|
"universalify": "^2.0.0"
|
||||||
},
|
},
|
||||||
@@ -1424,7 +1428,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10.0.0"
|
"node": ">= 10.0.0"
|
||||||
}
|
}
|
||||||
@@ -3773,6 +3776,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.17.0.tgz",
|
||||||
"integrity": "sha512-TLjSU9Otdpq0SpKHl1tD1Nc9MKhrsZbCFGot3EbCxRa8m1E5R1mMwoOjKMMM31IyF7fr+hPNHLpYfwbMKNusmg==",
|
"integrity": "sha512-TLjSU9Otdpq0SpKHl1tD1Nc9MKhrsZbCFGot3EbCxRa8m1E5R1mMwoOjKMMM31IyF7fr+hPNHLpYfwbMKNusmg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@libsql/core": "^0.17.0",
|
"@libsql/core": "^0.17.0",
|
||||||
"@libsql/hrana-client": "^0.9.0",
|
"@libsql/hrana-client": "^0.9.0",
|
||||||
@@ -4961,8 +4965,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
@@ -5184,6 +5187,7 @@
|
|||||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -5194,6 +5198,7 @@
|
|||||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.2.0"
|
"@types/react": "^19.2.0"
|
||||||
}
|
}
|
||||||
@@ -5411,6 +5416,7 @@
|
|||||||
"integrity": "sha512-CGJ25bc8fRi8Lod/3GHSvXRKi7nBo3kxh0ApW4yCjmrWmRmlT53B5E08XRSZRliygG0aVNxLrBEqPYdz/KcCtQ==",
|
"integrity": "sha512-CGJ25bc8fRi8Lod/3GHSvXRKi7nBo3kxh0ApW4yCjmrWmRmlT53B5E08XRSZRliygG0aVNxLrBEqPYdz/KcCtQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/utils": "4.0.18",
|
"@vitest/utils": "4.0.18",
|
||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
@@ -5607,6 +5613,7 @@
|
|||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
@@ -6129,6 +6136,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -6814,8 +6822,7 @@
|
|||||||
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/cross-env": {
|
"node_modules/cross-env": {
|
||||||
"version": "10.1.0",
|
"version": "10.1.0",
|
||||||
@@ -6952,7 +6959,23 @@
|
|||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/d3-cloud": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-cloud/-/d3-cloud-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-K0qBFkgystNlgFW/ufdwIES5kDiC8cGJxMw4ULzN9UU511v89A6HXs1X8vUPxqurehzqJZS5KzZI4c8McT+4UA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-dispatch": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-cloud/node_modules/d3-dispatch": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/data-uri-to-buffer": {
|
"node_modules/data-uri-to-buffer": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
@@ -7208,6 +7231,7 @@
|
|||||||
"integrity": "sha512-uOOBA3f+kW3o4KpSoMQ6SNpdXU7WtxlJRb9vCZgOvqhTz4b3GjcoWKstdisizNZLsylhTMv8TLHFPFW0Uxsj/g==",
|
"integrity": "sha512-uOOBA3f+kW3o4KpSoMQ6SNpdXU7WtxlJRb9vCZgOvqhTz4b3GjcoWKstdisizNZLsylhTMv8TLHFPFW0Uxsj/g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"app-builder-lib": "26.7.0",
|
"app-builder-lib": "26.7.0",
|
||||||
"builder-util": "26.4.1",
|
"builder-util": "26.4.1",
|
||||||
@@ -7289,8 +7313,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/dompurify": {
|
"node_modules/dompurify": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
@@ -7763,7 +7786,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/asar": "^3.2.1",
|
"@electron/asar": "^3.2.1",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
@@ -7784,7 +7806,6 @@
|
|||||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.1.2",
|
"graceful-fs": "^4.1.2",
|
||||||
"jsonfile": "^4.0.0",
|
"jsonfile": "^4.0.0",
|
||||||
@@ -7918,6 +7939,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
},
|
},
|
||||||
@@ -8830,7 +8852,7 @@
|
|||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
@@ -9137,6 +9159,7 @@
|
|||||||
"integrity": "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA==",
|
"integrity": "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@acemir/cssom": "^0.9.31",
|
"@acemir/cssom": "^0.9.31",
|
||||||
"@asamuzakjp/dom-selector": "^6.7.6",
|
"@asamuzakjp/dom-selector": "^6.7.6",
|
||||||
@@ -9428,7 +9451,6 @@
|
|||||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"lz-string": "bin/bin.js"
|
"lz-string": "bin/bin.js"
|
||||||
}
|
}
|
||||||
@@ -10669,7 +10691,6 @@
|
|||||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.6"
|
"minimist": "^1.2.6"
|
||||||
},
|
},
|
||||||
@@ -10682,6 +10703,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
|
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
|
||||||
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
|
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dompurify": "3.2.7",
|
"dompurify": "3.2.7",
|
||||||
"marked": "14.0.0"
|
"marked": "14.0.0"
|
||||||
@@ -11179,6 +11201,7 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -11254,7 +11277,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^9.4.0"
|
"commander": "^9.4.0"
|
||||||
},
|
},
|
||||||
@@ -11272,7 +11294,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.20.0 || >=14"
|
"node": "^12.20.0 || >=14"
|
||||||
}
|
}
|
||||||
@@ -11283,7 +11304,6 @@
|
|||||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^5.0.1",
|
"ansi-regex": "^5.0.1",
|
||||||
"ansi-styles": "^5.0.0",
|
"ansi-styles": "^5.0.0",
|
||||||
@@ -11299,7 +11319,6 @@
|
|||||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
},
|
},
|
||||||
@@ -11454,6 +11473,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
|
||||||
"integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
|
"integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"orderedmap": "^2.0.0"
|
"orderedmap": "^2.0.0"
|
||||||
}
|
}
|
||||||
@@ -11487,6 +11507,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
|
||||||
"integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
|
"integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"prosemirror-model": "^1.0.0",
|
"prosemirror-model": "^1.0.0",
|
||||||
"prosemirror-transform": "^1.0.0",
|
"prosemirror-transform": "^1.0.0",
|
||||||
@@ -11520,6 +11541,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.6.tgz",
|
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.6.tgz",
|
||||||
"integrity": "sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg==",
|
"integrity": "sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"prosemirror-model": "^1.20.0",
|
"prosemirror-model": "^1.20.0",
|
||||||
"prosemirror-state": "^1.0.0",
|
"prosemirror-state": "^1.0.0",
|
||||||
@@ -11597,6 +11619,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -11606,6 +11629,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
|
||||||
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -11635,8 +11659,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.18.0",
|
"version": "0.18.0",
|
||||||
@@ -11898,7 +11921,6 @@
|
|||||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"glob": "^7.1.3"
|
"glob": "^7.1.3"
|
||||||
},
|
},
|
||||||
@@ -12011,7 +12033,7 @@
|
|||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/sanitize-filename": {
|
"node_modules/sanitize-filename": {
|
||||||
@@ -12616,7 +12638,6 @@
|
|||||||
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"rimraf": "~2.6.2"
|
"rimraf": "~2.6.2"
|
||||||
@@ -12883,7 +12904,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "0BSD"
|
"license": "0BSD",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/tsx": {
|
"node_modules/tsx": {
|
||||||
"version": "4.21.0",
|
"version": "4.21.0",
|
||||||
@@ -13420,6 +13442,7 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -13700,6 +13723,7 @@
|
|||||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.27.0",
|
"esbuild": "^0.27.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -14259,6 +14283,7 @@
|
|||||||
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
|
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/expect": "4.0.18",
|
"@vitest/expect": "4.0.18",
|
||||||
"@vitest/mocker": "4.0.18",
|
"@vitest/mocker": "4.0.18",
|
||||||
@@ -14336,6 +14361,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz",
|
||||||
"integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==",
|
"integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.5.28",
|
"@vue/compiler-dom": "3.5.28",
|
||||||
"@vue/compiler-sfc": "3.5.28",
|
"@vue/compiler-sfc": "3.5.28",
|
||||||
|
|||||||
@@ -70,6 +70,7 @@
|
|||||||
"@picocss/pico": "^2.1.1",
|
"@picocss/pico": "^2.1.1",
|
||||||
"@xmldom/xmldom": "^0.8.11",
|
"@xmldom/xmldom": "^0.8.11",
|
||||||
"chokidar": "^5.0.0",
|
"chokidar": "^5.0.0",
|
||||||
|
"d3-cloud": "^1.2.8",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"drizzle-orm": "^0.45.1",
|
"drizzle-orm": "^0.45.1",
|
||||||
"dropbox": "^10.34.0",
|
"dropbox": "^10.34.0",
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ export class BlogGenerationEngine {
|
|||||||
pico_stylesheet_href: getPicoStylesheetHref(sanitizePicoTheme(options.picoTheme)),
|
pico_stylesheet_href: getPicoStylesheetHref(sanitizePicoTheme(options.picoTheme)),
|
||||||
};
|
};
|
||||||
|
|
||||||
const pageRenderer = new PageRenderer(this.mediaEngine, this.postMediaEngine);
|
const pageRenderer = new PageRenderer(this.mediaEngine, this.postMediaEngine, this.postEngine);
|
||||||
const rewriteContext = this.buildHtmlRewriteContext(publishedPosts);
|
const rewriteContext = this.buildHtmlRewriteContext(publishedPosts);
|
||||||
|
|
||||||
let pagesGenerated = 0;
|
let pagesGenerated = 0;
|
||||||
|
|||||||
@@ -107,6 +107,64 @@ export interface PostMediaEngineContract {
|
|||||||
|
|
||||||
export interface PostEngineContract {
|
export interface PostEngineContract {
|
||||||
getPost: (id: string) => Promise<PostData | null>;
|
getPost: (id: string) => Promise<PostData | null>;
|
||||||
|
getPostsFiltered?: (filter: { status?: 'draft' | 'published' | 'archived' }) => Promise<PostData[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TagUsageEntry {
|
||||||
|
tag: string;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TagCloudOrientationMode = 'horizontal' | 'mixed-hv' | 'mixed-diagonal';
|
||||||
|
|
||||||
|
export function normalizeTagCloudOrientation(value: string | undefined): TagCloudOrientationMode {
|
||||||
|
const normalized = (value || '').trim().toLowerCase();
|
||||||
|
|
||||||
|
if (normalized === 'mixed_hv' || normalized === 'mixed-hv' || normalized === 'hv' || normalized === 'horizontal_vertical') {
|
||||||
|
return 'mixed-hv';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized === 'mixed_diagonal' || normalized === 'mixed-diagonal' || normalized === 'diagonal' || normalized === 'diag') {
|
||||||
|
return 'mixed-diagonal';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'horizontal';
|
||||||
|
}
|
||||||
|
|
||||||
|
function interpolateChannel(start: number, end: number, t: number): number {
|
||||||
|
return Math.round(start + ((end - start) * t));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveTagCloudColor(normalizedRatio: number): string {
|
||||||
|
if (normalizedRatio >= 1) {
|
||||||
|
return 'red';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedRatio <= 0) {
|
||||||
|
return 'blue';
|
||||||
|
}
|
||||||
|
|
||||||
|
const palette = [
|
||||||
|
[0, 0, 255],
|
||||||
|
[0, 128, 0],
|
||||||
|
[255, 255, 0],
|
||||||
|
[255, 165, 0],
|
||||||
|
[255, 0, 0],
|
||||||
|
];
|
||||||
|
|
||||||
|
const scaled = normalizedRatio * (palette.length - 1);
|
||||||
|
const lowerIndex = Math.floor(scaled);
|
||||||
|
const upperIndex = Math.min(palette.length - 1, lowerIndex + 1);
|
||||||
|
const segmentRatio = scaled - lowerIndex;
|
||||||
|
|
||||||
|
const lowerColor = palette[lowerIndex];
|
||||||
|
const upperColor = palette[upperIndex];
|
||||||
|
|
||||||
|
const red = interpolateChannel(lowerColor[0], upperColor[0], segmentRatio);
|
||||||
|
const green = interpolateChannel(lowerColor[1], upperColor[1], segmentRatio);
|
||||||
|
const blue = interpolateChannel(lowerColor[2], upperColor[2], segmentRatio);
|
||||||
|
|
||||||
|
return `rgb(${red},${green},${blue})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PREVIEW_ASSETS: Record<string, { modulePath: string; contentType: string }> = {
|
export const PREVIEW_ASSETS: Record<string, { modulePath: string; contentType: string }> = {
|
||||||
@@ -131,6 +189,10 @@ export const PREVIEW_ASSETS: Record<string, { modulePath: string; contentType: s
|
|||||||
modulePath: 'lightbox2/dist/js/lightbox-plus-jquery.min.js',
|
modulePath: 'lightbox2/dist/js/lightbox-plus-jquery.min.js',
|
||||||
contentType: 'application/javascript; charset=utf-8',
|
contentType: 'application/javascript; charset=utf-8',
|
||||||
},
|
},
|
||||||
|
'd3.layout.cloud.js': {
|
||||||
|
modulePath: 'd3-cloud/build/d3.layout.cloud.js',
|
||||||
|
contentType: 'application/javascript; charset=utf-8',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PREVIEW_IMAGE_ASSETS = {
|
export const PREVIEW_IMAGE_ASSETS = {
|
||||||
@@ -377,6 +439,44 @@ export function renderPhotoArchiveMacro(params: Record<string, string>, mediaIte
|
|||||||
return `<div class="${rootClasses.join(' ')}" ${dataAttrs.join(' ')}><div class="photo-archive-container">${monthsHtml}</div></div>`;
|
return `<div class="${rootClasses.join(' ')}" ${dataAttrs.join(' ')}><div class="photo-archive-container">${monthsHtml}</div></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function renderTagCloudMacro(params: Record<string, string>, tagUsage: TagUsageEntry[]): string {
|
||||||
|
const widthParam = parseIntegerParam(params.width);
|
||||||
|
const heightParam = parseIntegerParam(params.height);
|
||||||
|
const orientation = normalizeTagCloudOrientation(params.orientation);
|
||||||
|
const width = widthParam && widthParam >= 320 && widthParam <= 1600 ? widthParam : 900;
|
||||||
|
const height = heightParam && heightParam >= 180 && heightParam <= 900 ? heightParam : 420;
|
||||||
|
|
||||||
|
if (tagUsage.length === 0) {
|
||||||
|
return `<div class="macro-tag-cloud" data-tag-cloud="true" data-orientation="${orientation}"><div class="tag-cloud-empty">No tags found.</div></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minCount = Math.min(...tagUsage.map((entry) => entry.count));
|
||||||
|
const maxCount = Math.max(...tagUsage.map((entry) => entry.count));
|
||||||
|
const minFont = 14;
|
||||||
|
const maxFont = 56;
|
||||||
|
|
||||||
|
const words = tagUsage.map((entry) => {
|
||||||
|
const ratio = maxCount === minCount
|
||||||
|
? 1
|
||||||
|
: (entry.count - minCount) / (maxCount - minCount);
|
||||||
|
const normalizedSize = maxCount === minCount
|
||||||
|
? Math.round((minFont + maxFont) / 2)
|
||||||
|
: Math.round(minFont + ((entry.count - minCount) / (maxCount - minCount)) * (maxFont - minFont));
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: entry.tag,
|
||||||
|
size: normalizedSize,
|
||||||
|
count: entry.count,
|
||||||
|
color: resolveTagCloudColor(ratio),
|
||||||
|
url: `/tag/${encodeURIComponent(entry.tag)}/`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const wordsJson = escapeHtml(JSON.stringify(words));
|
||||||
|
|
||||||
|
return `<div class="macro-tag-cloud" data-tag-cloud="true" data-orientation="${orientation}" data-tag-cloud-words="${wordsJson}" data-width="${width}" data-height="${height}"><svg class="tag-cloud-canvas" viewBox="0 0 ${width} ${height}" preserveAspectRatio="xMidYMid meet" aria-label="Tag cloud"></svg></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
export function isExternalOrSpecialUrl(value: string): boolean {
|
export function isExternalOrSpecialUrl(value: string): boolean {
|
||||||
const normalized = value.trim();
|
const normalized = value.trim();
|
||||||
if (!normalized) return false;
|
if (!normalized) return false;
|
||||||
@@ -479,6 +579,7 @@ export function renderMacro(
|
|||||||
postId: string,
|
postId: string,
|
||||||
mediaItems: MediaData[],
|
mediaItems: MediaData[],
|
||||||
linkedMediaIds: Set<string> | null,
|
linkedMediaIds: Set<string> | null,
|
||||||
|
tagUsage: TagUsageEntry[],
|
||||||
): string {
|
): string {
|
||||||
const normalizedName = normalizeMacroName(name);
|
const normalizedName = normalizeMacroName(name);
|
||||||
|
|
||||||
@@ -504,6 +605,10 @@ export function renderMacro(
|
|||||||
return renderPhotoArchiveMacro(params, mediaItems);
|
return renderPhotoArchiveMacro(params, mediaItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (normalizedName === 'tag_cloud') {
|
||||||
|
return renderTagCloudMacro(params, tagUsage);
|
||||||
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,11 +686,13 @@ export function recordToMap(record: unknown): Map<string, string> {
|
|||||||
export class PageRenderer {
|
export class PageRenderer {
|
||||||
private readonly mediaEngine: MediaEngineContract;
|
private readonly mediaEngine: MediaEngineContract;
|
||||||
private readonly postMediaEngine: PostMediaEngineContract;
|
private readonly postMediaEngine: PostMediaEngineContract;
|
||||||
|
private readonly postEngineForMacros?: PostEngineContract;
|
||||||
private readonly liquid: Liquid;
|
private readonly liquid: Liquid;
|
||||||
|
|
||||||
constructor(mediaEngine: MediaEngineContract, postMediaEngine: PostMediaEngineContract) {
|
constructor(mediaEngine: MediaEngineContract, postMediaEngine: PostMediaEngineContract, postEngineForMacros?: PostEngineContract) {
|
||||||
this.mediaEngine = mediaEngine;
|
this.mediaEngine = mediaEngine;
|
||||||
this.postMediaEngine = postMediaEngine;
|
this.postMediaEngine = postMediaEngine;
|
||||||
|
this.postEngineForMacros = postEngineForMacros;
|
||||||
|
|
||||||
const templateRoots = [
|
const templateRoots = [
|
||||||
path.resolve(__dirname, 'templates'),
|
path.resolve(__dirname, 'templates'),
|
||||||
@@ -608,10 +715,15 @@ export class PageRenderer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const needsMediaLookup = /\[\[(gallery|photo_archive|photo_album)\b/i.test(content);
|
const needsMediaLookup = /\[\[(gallery|photo_archive|photo_album)\b/i.test(content);
|
||||||
|
const needsTagCloudLookup = /\[\[(tag_cloud)\b/i.test(content);
|
||||||
const mediaItems = needsMediaLookup
|
const mediaItems = needsMediaLookup
|
||||||
? await this.mediaEngine.getAllMedia().catch(() => [] as MediaData[])
|
? await this.mediaEngine.getAllMedia().catch(() => [] as MediaData[])
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
const tagUsage = needsTagCloudLookup
|
||||||
|
? await this.getTagUsageData()
|
||||||
|
: [];
|
||||||
|
|
||||||
const linkedMediaIds = needsMediaLookup && postId
|
const linkedMediaIds = needsMediaLookup && postId
|
||||||
? await this.postMediaEngine.getLinkedMediaDataForPost(postId)
|
? await this.postMediaEngine.getLinkedMediaDataForPost(postId)
|
||||||
.then((links) => new Set<string>(links.map((link) => link.media?.id).filter((id): id is string => typeof id === 'string' && id.length > 0)))
|
.then((links) => new Set<string>(links.map((link) => link.media?.id).filter((id): id is string => typeof id === 'string' && id.length > 0)))
|
||||||
@@ -620,7 +732,7 @@ export class PageRenderer {
|
|||||||
|
|
||||||
const withMacros = content.replace(/\[\[(\w+)(?:\s+([^\]]+))?\]\]/g, (_match, macroName: string, rawParams: string | undefined) => {
|
const withMacros = content.replace(/\[\[(\w+)(?:\s+([^\]]+))?\]\]/g, (_match, macroName: string, rawParams: string | undefined) => {
|
||||||
const params = parseMacroParams(rawParams);
|
const params = parseMacroParams(rawParams);
|
||||||
return renderMacro(macroName.toLowerCase(), params, postId, mediaItems, linkedMediaIds);
|
return renderMacro(macroName.toLowerCase(), params, postId, mediaItems, linkedMediaIds, tagUsage);
|
||||||
});
|
});
|
||||||
|
|
||||||
const markdownHtml = await marked.parse(withMacros, { async: true, gfm: true, breaks: false });
|
const markdownHtml = await marked.parse(withMacros, { async: true, gfm: true, breaks: false });
|
||||||
@@ -628,6 +740,32 @@ export class PageRenderer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getTagUsageData(): Promise<TagUsageEntry[]> {
|
||||||
|
if (!this.postEngineForMacros?.getPostsFiltered) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const posts = await this.postEngineForMacros.getPostsFiltered({ status: 'published' }).catch(() => [] as PostData[]);
|
||||||
|
const tagCounts = new Map<string, number>();
|
||||||
|
|
||||||
|
for (const post of posts) {
|
||||||
|
const postTags = Array.isArray(post.tags) ? post.tags : [];
|
||||||
|
const uniqueTags = new Set<string>(
|
||||||
|
postTags
|
||||||
|
.map((tag) => tag.trim())
|
||||||
|
.filter((tag) => tag.length > 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const tag of uniqueTags) {
|
||||||
|
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(tagCounts.entries())
|
||||||
|
.map(([tag, count]) => ({ tag, count }))
|
||||||
|
.sort((a, b) => (b.count - a.count) || a.tag.localeCompare(b.tag));
|
||||||
|
}
|
||||||
|
|
||||||
buildListTemplateContext(
|
buildListTemplateContext(
|
||||||
posts: PostData[],
|
posts: PostData[],
|
||||||
rewriteContext: HtmlRewriteContext,
|
rewriteContext: HtmlRewriteContext,
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export class PreviewServer {
|
|||||||
projectDescription: activeProject?.description ?? undefined,
|
projectDescription: activeProject?.description ?? undefined,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
this.pageRenderer = new PageRenderer(this.mediaEngine, this.postMediaEngine);
|
this.pageRenderer = new PageRenderer(this.mediaEngine, this.postMediaEngine, this.postEngine);
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(preferredPort = 0): Promise<number> {
|
async start(preferredPort = 0): Promise<number> {
|
||||||
|
|||||||
@@ -6,5 +6,120 @@
|
|||||||
<link rel="stylesheet" href="{{ resolved_pico_stylesheet_href }}" />
|
<link rel="stylesheet" href="{{ resolved_pico_stylesheet_href }}" />
|
||||||
<link rel="stylesheet" href="/assets/lightbox.min.css" />
|
<link rel="stylesheet" href="/assets/lightbox.min.css" />
|
||||||
{% render 'partials/styles' %}
|
{% render 'partials/styles' %}
|
||||||
|
<script defer src="/assets/d3.layout.cloud.js"></script>
|
||||||
<script defer src="/assets/lightbox.min.js"></script>
|
<script defer src="/assets/lightbox.min.js"></script>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
function parseWords(rawWords) {
|
||||||
|
if (!rawWords || typeof rawWords !== 'string') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(rawWords);
|
||||||
|
return Array.isArray(parsed) ? parsed : [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawTagCloud(container) {
|
||||||
|
const cloudFactory = window.d3 && window.d3.layout && typeof window.d3.layout.cloud === 'function'
|
||||||
|
? window.d3.layout.cloud
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!cloudFactory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawWords = container.getAttribute('data-tag-cloud-words');
|
||||||
|
const words = parseWords(rawWords);
|
||||||
|
if (words.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = Number.parseInt(container.getAttribute('data-width') || '900', 10) || 900;
|
||||||
|
const height = Number.parseInt(container.getAttribute('data-height') || '420', 10) || 420;
|
||||||
|
const orientation = container.getAttribute('data-orientation') || 'horizontal';
|
||||||
|
|
||||||
|
const resolveRotation = () => {
|
||||||
|
if (orientation === 'mixed-hv') {
|
||||||
|
return Math.random() < 0.5 ? 0 : 90;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orientation === 'mixed-diagonal') {
|
||||||
|
const diagonalAngles = [-60, -30, 0, 30, 60, 90];
|
||||||
|
const index = Math.floor(Math.random() * diagonalAngles.length);
|
||||||
|
return diagonalAngles[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const svgNode = container.querySelector('svg.tag-cloud-canvas');
|
||||||
|
if (!svgNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (svgNode.firstChild) {
|
||||||
|
svgNode.removeChild(svgNode.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
cloudFactory()
|
||||||
|
.size([width, height])
|
||||||
|
.words(words.map((word) => ({ ...word })))
|
||||||
|
.padding(4)
|
||||||
|
.rotate(() => resolveRotation())
|
||||||
|
.font('sans-serif')
|
||||||
|
.fontSize((word) => word.size)
|
||||||
|
.on('end', (layoutWords) => {
|
||||||
|
svgNode.setAttribute('viewBox', `0 0 ${width} ${height}`);
|
||||||
|
svgNode.setAttribute('preserveAspectRatio', 'xMidYMid meet');
|
||||||
|
|
||||||
|
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
||||||
|
group.setAttribute('transform', `translate(${width / 2},${height / 2})`);
|
||||||
|
|
||||||
|
for (const word of layoutWords) {
|
||||||
|
const textNode = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||||
|
textNode.textContent = word.text;
|
||||||
|
textNode.setAttribute('text-anchor', 'middle');
|
||||||
|
textNode.setAttribute('transform', `translate(${word.x},${word.y})rotate(${word.rotate || 0})`);
|
||||||
|
textNode.style.fontFamily = 'sans-serif';
|
||||||
|
textNode.style.fontSize = `${word.size}px`;
|
||||||
|
textNode.style.fill = typeof word.color === 'string' && word.color.length > 0
|
||||||
|
? word.color
|
||||||
|
: 'currentColor';
|
||||||
|
textNode.style.cursor = 'pointer';
|
||||||
|
textNode.style.opacity = '0.9';
|
||||||
|
|
||||||
|
const titleNode = document.createElementNS('http://www.w3.org/2000/svg', 'title');
|
||||||
|
titleNode.textContent = `${word.text} (${word.count})`;
|
||||||
|
textNode.appendChild(titleNode);
|
||||||
|
|
||||||
|
textNode.addEventListener('click', () => {
|
||||||
|
if (word && typeof word.url === 'string' && word.url.length > 0) {
|
||||||
|
window.location.assign(word.url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
group.appendChild(textNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
svgNode.appendChild(group);
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
function initTagClouds() {
|
||||||
|
const containers = document.querySelectorAll('[data-tag-cloud="true"]');
|
||||||
|
containers.forEach((container) => drawTagCloud(container));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', initTagClouds, { once: true });
|
||||||
|
} else {
|
||||||
|
initTagClouds();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
main { display: grid; gap: 1rem; }
|
main { display: grid; gap: 1rem; }
|
||||||
.post { border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); padding: 1rem; background: var(--pico-card-background-color, var(--card-background-color)); }
|
.post { border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); padding: 1rem; background: var(--pico-card-background-color, var(--card-background-color)); }
|
||||||
.post iframe { width: 100%; min-height: 20rem; }
|
.post iframe { width: 100%; min-height: 20rem; }
|
||||||
.macro-gallery, .macro-photo-archive { border: 1px dashed var(--pico-muted-border-color, var(--muted-border-color)); padding: .75rem; margin: 1rem 0; }
|
.macro-gallery, .macro-photo-archive, .macro-tag-cloud { border: 1px dashed var(--pico-muted-border-color, var(--muted-border-color)); padding: .75rem; margin: 1rem 0; }
|
||||||
.gallery-container { display: grid; gap: .5rem; }
|
.gallery-container { display: grid; gap: .5rem; }
|
||||||
.macro-gallery.gallery-cols-1 .gallery-container { grid-template-columns: 1fr; }
|
.macro-gallery.gallery-cols-1 .gallery-container { grid-template-columns: 1fr; }
|
||||||
.macro-gallery.gallery-cols-2 .gallery-container { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
.macro-gallery.gallery-cols-2 .gallery-container { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
@@ -22,6 +22,9 @@
|
|||||||
.photo-archive-month-label span { writing-mode: vertical-rl; transform: rotate(180deg); letter-spacing: .08em; text-transform: uppercase; color: var(--pico-muted-color, var(--muted-color)); }
|
.photo-archive-month-label span { writing-mode: vertical-rl; transform: rotate(180deg); letter-spacing: .08em; text-transform: uppercase; color: var(--pico-muted-color, var(--muted-color)); }
|
||||||
.photo-archive-gallery { display: grid; gap: .5rem; grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
.photo-archive-gallery { display: grid; gap: .5rem; grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||||
.photo-archive-single-month .photo-archive-gallery { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
.photo-archive-single-month .photo-archive-gallery { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
||||||
|
.macro-tag-cloud { min-height: 14rem; }
|
||||||
|
.tag-cloud-canvas { display: block; width: 100%; height: auto; min-height: 12rem; }
|
||||||
|
.tag-cloud-empty { color: var(--pico-muted-color, var(--muted-color)); font-style: italic; }
|
||||||
.archive-day-group { display: grid; grid-template-columns: 5.25rem 1fr; gap: 1.25rem; align-items: stretch; }
|
.archive-day-group { display: grid; grid-template-columns: 5.25rem 1fr; gap: 1.25rem; align-items: stretch; }
|
||||||
.archive-day-marker { display: flex; justify-content: center; align-items: center; color: var(--pico-muted-color, var(--muted-color)); }
|
.archive-day-marker { display: flex; justify-content: center; align-items: center; color: var(--pico-muted-color, var(--muted-color)); }
|
||||||
.archive-day-marker span { writing-mode: vertical-rl; transform: rotate(180deg); letter-spacing: .16em; font-size: 1.05rem; font-weight: 600; text-transform: uppercase; }
|
.archive-day-marker span { writing-mode: vertical-rl; transform: rotate(180deg); letter-spacing: .16em; font-size: 1.05rem; font-weight: 600; text-transform: uppercase; }
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ describe('PreviewServer', () => {
|
|||||||
expect(rootHtml).toContain('href="/assets/pico.min.css"');
|
expect(rootHtml).toContain('href="/assets/pico.min.css"');
|
||||||
expect(rootHtml).toContain('href="/assets/lightbox.min.css"');
|
expect(rootHtml).toContain('href="/assets/lightbox.min.css"');
|
||||||
expect(rootHtml).toContain('src="/assets/lightbox.min.js"');
|
expect(rootHtml).toContain('src="/assets/lightbox.min.js"');
|
||||||
|
expect(rootHtml).toContain('src="/assets/d3.layout.cloud.js"');
|
||||||
expect(rootHtml).not.toContain('cdn.jsdelivr.net');
|
expect(rootHtml).not.toContain('cdn.jsdelivr.net');
|
||||||
|
|
||||||
const picoResponse = await fetch(`${server.getBaseUrl()}/assets/pico.min.css`);
|
const picoResponse = await fetch(`${server.getBaseUrl()}/assets/pico.min.css`);
|
||||||
@@ -191,6 +192,10 @@ describe('PreviewServer', () => {
|
|||||||
expect(lightboxJsResponse.status).toBe(200);
|
expect(lightboxJsResponse.status).toBe(200);
|
||||||
expect(lightboxJsResponse.headers.get('content-type')).toContain('application/javascript');
|
expect(lightboxJsResponse.headers.get('content-type')).toContain('application/javascript');
|
||||||
|
|
||||||
|
const d3CloudJsResponse = await fetch(`${server.getBaseUrl()}/assets/d3.layout.cloud.js`);
|
||||||
|
expect(d3CloudJsResponse.status).toBe(200);
|
||||||
|
expect(d3CloudJsResponse.headers.get('content-type')).toContain('application/javascript');
|
||||||
|
|
||||||
const lightboxPrevImageResponse = await fetch(`${server.getBaseUrl()}/images/prev.png`);
|
const lightboxPrevImageResponse = await fetch(`${server.getBaseUrl()}/images/prev.png`);
|
||||||
expect(lightboxPrevImageResponse.status).toBe(200);
|
expect(lightboxPrevImageResponse.status).toBe(200);
|
||||||
expect(lightboxPrevImageResponse.headers.get('content-type')).toContain('image/png');
|
expect(lightboxPrevImageResponse.headers.get('content-type')).toContain('image/png');
|
||||||
@@ -200,6 +205,90 @@ describe('PreviewServer', () => {
|
|||||||
expect(lightboxLoadingImageResponse.headers.get('content-type')).toContain('image/gif');
|
expect(lightboxLoadingImageResponse.headers.get('content-type')).toContain('image/gif');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders tag_cloud macro with normalized tag usage and tag archive links', async () => {
|
||||||
|
const posts = [
|
||||||
|
makePost({
|
||||||
|
id: 'tags-1',
|
||||||
|
slug: 'tag-cloud-source',
|
||||||
|
title: 'Tag Cloud Source',
|
||||||
|
tags: ['TypeScript', 'Electron'],
|
||||||
|
content: '[[tag_cloud]]',
|
||||||
|
}),
|
||||||
|
makePost({
|
||||||
|
id: 'tags-2',
|
||||||
|
slug: 'second',
|
||||||
|
title: 'Second',
|
||||||
|
tags: ['TypeScript'],
|
||||||
|
}),
|
||||||
|
makePost({
|
||||||
|
id: 'tags-3',
|
||||||
|
slug: 'third',
|
||||||
|
title: 'Third',
|
||||||
|
tags: ['Electron', 'SQLite', 'TypeScript'],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
server = new PreviewServer({
|
||||||
|
postEngine: makeEngine(posts),
|
||||||
|
settingsEngine: makeSettings(50),
|
||||||
|
getActiveProjectContext: async () => ({ projectId: 'default' }),
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.start(0);
|
||||||
|
|
||||||
|
const response = await fetch(`${server.getBaseUrl()}/posts/tag-cloud-source`);
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
const html = await response.text();
|
||||||
|
|
||||||
|
expect(html).toContain('class="macro-tag-cloud"');
|
||||||
|
expect(html).toContain('data-tag-cloud="true"');
|
||||||
|
expect(html).toContain('data-orientation="horizontal"');
|
||||||
|
expect(html).toContain('TypeScript');
|
||||||
|
expect(html).toContain('/tag/TypeScript/');
|
||||||
|
expect(html).toContain('/tag/Electron/');
|
||||||
|
expect(html).toContain('/tag/SQLite/');
|
||||||
|
expect(html).toContain('"color":"rgb(');
|
||||||
|
expect(html).toContain('"color":"red"');
|
||||||
|
expect(html).toContain('"color":"blue"');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports tag_cloud orientation parameter modes', async () => {
|
||||||
|
const posts = [
|
||||||
|
makePost({
|
||||||
|
id: 'orientation-1',
|
||||||
|
slug: 'orientation-hv',
|
||||||
|
title: 'Orientation HV',
|
||||||
|
tags: ['alpha', 'beta'],
|
||||||
|
content: '[[tag_cloud orientation="mixed_hv"]]',
|
||||||
|
}),
|
||||||
|
makePost({
|
||||||
|
id: 'orientation-2',
|
||||||
|
slug: 'orientation-diagonal',
|
||||||
|
title: 'Orientation Diagonal',
|
||||||
|
tags: ['alpha'],
|
||||||
|
content: '[[tag_cloud orientation="mixed_diagonal"]]',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
server = new PreviewServer({
|
||||||
|
postEngine: makeEngine(posts),
|
||||||
|
settingsEngine: makeSettings(50),
|
||||||
|
getActiveProjectContext: async () => ({ projectId: 'default' }),
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.start(0);
|
||||||
|
|
||||||
|
const hvResponse = await fetch(`${server.getBaseUrl()}/posts/orientation-hv`);
|
||||||
|
expect(hvResponse.status).toBe(200);
|
||||||
|
const hvHtml = await hvResponse.text();
|
||||||
|
expect(hvHtml).toContain('data-orientation="mixed-hv"');
|
||||||
|
|
||||||
|
const diagonalResponse = await fetch(`${server.getBaseUrl()}/posts/orientation-diagonal`);
|
||||||
|
expect(diagonalResponse.status).toBe(200);
|
||||||
|
const diagonalHtml = await diagonalResponse.text();
|
||||||
|
expect(diagonalHtml).toContain('data-orientation="mixed-diagonal"');
|
||||||
|
});
|
||||||
|
|
||||||
it('uses selected pico theme stylesheet from project metadata', async () => {
|
it('uses selected pico theme stylesheet from project metadata', async () => {
|
||||||
server = new PreviewServer({
|
server = new PreviewServer({
|
||||||
postEngine: makeEngine([makePost()]),
|
postEngine: makeEngine([makePost()]),
|
||||||
|
|||||||
@@ -16,6 +16,18 @@ describe('documentation structure and presentation', () => {
|
|||||||
expect(markdown).toContain('## Who this guide is for');
|
expect(markdown).toContain('## Who this guide is for');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('documents all supported macros in the user guide', () => {
|
||||||
|
const docPath = path.resolve(process.cwd(), 'DOCUMENTATION.md');
|
||||||
|
const markdown = readFileSync(docPath, 'utf8');
|
||||||
|
|
||||||
|
expect(markdown).toContain('## Using macros');
|
||||||
|
expect(markdown).toContain('[[youtube');
|
||||||
|
expect(markdown).toContain('[[vimeo');
|
||||||
|
expect(markdown).toContain('[[gallery');
|
||||||
|
expect(markdown).toContain('[[photo_archive');
|
||||||
|
expect(markdown).toContain('[[tag_cloud');
|
||||||
|
});
|
||||||
|
|
||||||
it('scopes Pico conditional styling to the documentation view', () => {
|
it('scopes Pico conditional styling to the documentation view', () => {
|
||||||
const viewPath = path.resolve(
|
const viewPath = path.resolve(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
|
|||||||
Reference in New Issue
Block a user