mirror of
https://github.com/quartz-community/note-properties.git
synced 2026-07-22 02:50:26 +00:00
feat: implement note-properties plugin
Move frontmatter processing from core Quartz into a standalone plugin. Parse YAML/TOML frontmatter, extract links from property values, and render an Obsidian-like properties panel in the page. Supports: includeAll/includedProperties/excludedProperties filtering, hidePropertiesView mode, wikilink/markdown link resolution in values, collapsible panel with client-side state persistence, i18n.
This commit is contained in:
parent
21bf54cf43
commit
107fe07c12
15 changed files with 881 additions and 817 deletions
509
package-lock.json
generated
509
package-lock.json
generated
|
|
@ -1,26 +1,25 @@
|
|||
{
|
||||
"name": "@quartz-community/plugin-template",
|
||||
"name": "@quartz-community/note-properties",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@quartz-community/plugin-template",
|
||||
"name": "@quartz-community/note-properties",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@quartz-community/types": "github:quartz-community/types",
|
||||
"mdast-util-find-and-replace": "^3.0.1",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"toml": "^3.0.0",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"vfile": "^6.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"@types/node": "^24.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
|
|
@ -1108,6 +1107,13 @@
|
|||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/js-yaml": {
|
||||
"version": "4.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
|
||||
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/mdast": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
|
||||
|
|
@ -1546,7 +1552,6 @@
|
|||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true,
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/array-union": {
|
||||
|
|
@ -1645,16 +1650,6 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/ccount": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
|
||||
"integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
|
||||
|
|
@ -2095,6 +2090,19 @@
|
|||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"license": "BSD-2-Clause",
|
||||
"bin": {
|
||||
"esparse": "bin/esparse.js",
|
||||
"esvalidate": "bin/esvalidate.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/esquery": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
|
||||
|
|
@ -2167,6 +2175,18 @@
|
|||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/extend-shallow": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-extendable": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
|
|
@ -2228,6 +2248,19 @@
|
|||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fault": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz",
|
||||
"integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"format": "^0.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
||||
|
|
@ -2305,6 +2338,14 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/format": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
|
||||
"integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
|
||||
"engines": {
|
||||
"node": ">=0.4.x"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
|
|
@ -2327,12 +2368,6 @@
|
|||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/github-slugger": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
|
||||
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
|
|
@ -2436,6 +2471,43 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/gray-matter": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
|
||||
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-yaml": "^3.13.1",
|
||||
"kind-of": "^6.0.2",
|
||||
"section-matter": "^1.0.0",
|
||||
"strip-bom-string": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/js-yaml": {
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
|
|
@ -2446,32 +2518,6 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-heading-rank": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz",
|
||||
"integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-to-string": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz",
|
||||
"integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
|
|
@ -2528,6 +2574,15 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/is-extendable": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
||||
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
|
|
@ -2604,7 +2659,6 @@
|
|||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
|
|
@ -2644,6 +2698,15 @@
|
|||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/levn": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
|
|
@ -2738,44 +2801,6 @@
|
|||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-table": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
|
||||
"integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-find-and-replace": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
|
||||
"integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"unist-util-is": "^6.0.0",
|
||||
"unist-util-visit-parents": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-from-markdown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
|
||||
|
|
@ -2800,105 +2825,34 @@
|
|||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-gfm": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
|
||||
"integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"mdast-util-gfm-autolink-literal": "^2.0.0",
|
||||
"mdast-util-gfm-footnote": "^2.0.0",
|
||||
"mdast-util-gfm-strikethrough": "^2.0.0",
|
||||
"mdast-util-gfm-table": "^2.0.0",
|
||||
"mdast-util-gfm-task-list-item": "^2.0.0",
|
||||
"mdast-util-to-markdown": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-gfm-autolink-literal": {
|
||||
"node_modules/mdast-util-frontmatter": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
|
||||
"integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz",
|
||||
"integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"ccount": "^2.0.0",
|
||||
"devlop": "^1.0.0",
|
||||
"mdast-util-find-and-replace": "^3.0.0",
|
||||
"micromark-util-character": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-gfm-footnote": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz",
|
||||
"integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"devlop": "^1.1.0",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"mdast-util-to-markdown": "^2.0.0",
|
||||
"micromark-util-normalize-identifier": "^2.0.0"
|
||||
"micromark-extension-frontmatter": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-gfm-strikethrough": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
|
||||
"integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
|
||||
"node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"mdast-util-to-markdown": "^2.0.0"
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-gfm-table": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
|
||||
"integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"devlop": "^1.0.0",
|
||||
"markdown-table": "^3.0.0",
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"mdast-util-to-markdown": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-gfm-task-list-item": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
|
||||
"integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"devlop": "^1.0.0",
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"mdast-util-to-markdown": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-phrasing": {
|
||||
|
|
@ -3028,118 +2982,13 @@
|
|||
"micromark-util-types": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-gfm": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
|
||||
"integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"micromark-extension-gfm-autolink-literal": "^2.0.0",
|
||||
"micromark-extension-gfm-footnote": "^2.0.0",
|
||||
"micromark-extension-gfm-strikethrough": "^2.0.0",
|
||||
"micromark-extension-gfm-table": "^2.0.0",
|
||||
"micromark-extension-gfm-tagfilter": "^2.0.0",
|
||||
"micromark-extension-gfm-task-list-item": "^2.0.0",
|
||||
"micromark-util-combine-extensions": "^2.0.0",
|
||||
"micromark-util-types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-gfm-autolink-literal": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
|
||||
"integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"micromark-util-character": "^2.0.0",
|
||||
"micromark-util-sanitize-uri": "^2.0.0",
|
||||
"micromark-util-symbol": "^2.0.0",
|
||||
"micromark-util-types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-gfm-footnote": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
|
||||
"integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"devlop": "^1.0.0",
|
||||
"micromark-core-commonmark": "^2.0.0",
|
||||
"micromark-factory-space": "^2.0.0",
|
||||
"micromark-util-character": "^2.0.0",
|
||||
"micromark-util-normalize-identifier": "^2.0.0",
|
||||
"micromark-util-sanitize-uri": "^2.0.0",
|
||||
"micromark-util-symbol": "^2.0.0",
|
||||
"micromark-util-types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-gfm-strikethrough": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
|
||||
"integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"devlop": "^1.0.0",
|
||||
"micromark-util-chunked": "^2.0.0",
|
||||
"micromark-util-classify-character": "^2.0.0",
|
||||
"micromark-util-resolve-all": "^2.0.0",
|
||||
"micromark-util-symbol": "^2.0.0",
|
||||
"micromark-util-types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-gfm-table": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
|
||||
"integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"devlop": "^1.0.0",
|
||||
"micromark-factory-space": "^2.0.0",
|
||||
"micromark-util-character": "^2.0.0",
|
||||
"micromark-util-symbol": "^2.0.0",
|
||||
"micromark-util-types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-gfm-tagfilter": {
|
||||
"node_modules/micromark-extension-frontmatter": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
|
||||
"integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz",
|
||||
"integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"micromark-util-types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-gfm-task-list-item": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
|
||||
"integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"devlop": "^1.0.0",
|
||||
"micromark-factory-space": "^2.0.0",
|
||||
"fault": "^2.0.0",
|
||||
"micromark-util-character": "^2.0.0",
|
||||
"micromark-util-symbol": "^2.0.0",
|
||||
"micromark-util-types": "^2.0.0"
|
||||
|
|
@ -3946,65 +3795,15 @@
|
|||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/rehype-slug": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz",
|
||||
"integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"github-slugger": "^2.0.0",
|
||||
"hast-util-heading-rank": "^3.0.0",
|
||||
"hast-util-to-string": "^3.0.0",
|
||||
"unist-util-visit": "^5.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-gfm": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
|
||||
"integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
|
||||
"node_modules/remark-frontmatter": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz",
|
||||
"integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"mdast-util-gfm": "^3.0.0",
|
||||
"micromark-extension-gfm": "^3.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"unified": "^11.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-parse": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
||||
"integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"micromark-util-types": "^2.0.0",
|
||||
"unified": "^11.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-stringify": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
|
||||
"integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"mdast-util-to-markdown": "^2.0.0",
|
||||
"mdast-util-frontmatter": "^2.0.0",
|
||||
"micromark-extension-frontmatter": "^2.0.0",
|
||||
"unified": "^11.0.0"
|
||||
},
|
||||
"funding": {
|
||||
|
|
@ -4119,6 +3918,19 @@
|
|||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/section-matter": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
|
||||
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"extend-shallow": "^2.0.1",
|
||||
"kind-of": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
|
|
@ -4192,6 +4004,12 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/stackback": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||
|
|
@ -4219,6 +4037,15 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-bom-string": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
|
||||
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||
|
|
@ -4404,6 +4231,12 @@
|
|||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/toml": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz",
|
||||
"integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tree-kill": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||
|
|
|
|||
108
package.json
108
package.json
|
|
@ -1,23 +1,22 @@
|
|||
{
|
||||
"name": "@quartz-community/plugin-template",
|
||||
"name": "@quartz-community/note-properties",
|
||||
"version": "0.1.0",
|
||||
"description": "Template repository for Quartz community plugins.",
|
||||
"description": "Frontmatter processing and note properties display for Quartz",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"author": "Quartz Community",
|
||||
"homepage": "https://quartz.jzhao.xyz",
|
||||
"homepage": "https://github.com/quartz-community/note-properties",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/quartz-community/plugin-template"
|
||||
"url": "https://github.com/quartz-community/note-properties"
|
||||
},
|
||||
"keywords": [
|
||||
"quartz",
|
||||
"quartz-plugin",
|
||||
"plugin-template",
|
||||
"remark",
|
||||
"rehype",
|
||||
"mdast",
|
||||
"hast"
|
||||
"note-properties",
|
||||
"frontmatter",
|
||||
"transformer",
|
||||
"component"
|
||||
],
|
||||
"files": [
|
||||
"dist",
|
||||
|
|
@ -67,17 +66,16 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@quartz-community/types": "github:quartz-community/types",
|
||||
"mdast-util-find-and-replace": "^3.0.1",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"toml": "^3.0.0",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"vfile": "^6.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"@types/node": "^24.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
|
|
@ -90,26 +88,70 @@
|
|||
"typescript": "^5.9.3",
|
||||
"vitest": "^2.1.9"
|
||||
},
|
||||
"quartz": {
|
||||
"name": "plugin-template",
|
||||
"displayName": "Plugin Template",
|
||||
"category": "transformer",
|
||||
"version": "1.0.0",
|
||||
"quartzVersion": ">=5.0.0",
|
||||
"dependencies": [],
|
||||
"defaultOrder": 50,
|
||||
"defaultEnabled": true,
|
||||
"defaultOptions": {},
|
||||
"components": {
|
||||
"ExampleComponent": {
|
||||
"displayName": "Example Component",
|
||||
"defaultPosition": "right",
|
||||
"defaultPriority": 50
|
||||
}
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22",
|
||||
"npm": ">=10.9.2"
|
||||
},
|
||||
"quartz": {
|
||||
"name": "note-properties",
|
||||
"displayName": "Note Properties",
|
||||
"category": [
|
||||
"transformer",
|
||||
"component"
|
||||
],
|
||||
"version": "1.0.0",
|
||||
"quartzVersion": ">=5.0.0",
|
||||
"dependencies": [],
|
||||
"defaultOrder": 5,
|
||||
"defaultEnabled": true,
|
||||
"defaultOptions": {
|
||||
"includeAll": false,
|
||||
"includedProperties": [
|
||||
"description",
|
||||
"tags",
|
||||
"aliases"
|
||||
],
|
||||
"excludedProperties": [],
|
||||
"hidePropertiesView": false,
|
||||
"delimiters": "---",
|
||||
"language": "yaml"
|
||||
},
|
||||
"optionSchema": {
|
||||
"includeAll": {
|
||||
"type": "boolean",
|
||||
"description": "Include all frontmatter properties in the display"
|
||||
},
|
||||
"includedProperties": {
|
||||
"type": "array",
|
||||
"description": "Properties to include when includeAll is false"
|
||||
},
|
||||
"excludedProperties": {
|
||||
"type": "array",
|
||||
"description": "Properties to exclude from display"
|
||||
},
|
||||
"hidePropertiesView": {
|
||||
"type": "boolean",
|
||||
"description": "Hide the visual properties panel while still processing frontmatter"
|
||||
},
|
||||
"delimiters": {
|
||||
"type": "string",
|
||||
"description": "Frontmatter delimiters"
|
||||
},
|
||||
"language": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
"yaml",
|
||||
"toml"
|
||||
],
|
||||
"description": "Frontmatter language"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"NotePropertiesComponent": {
|
||||
"displayName": "Note Properties",
|
||||
"defaultPosition": "beforeBody",
|
||||
"defaultPriority": 15
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
import type {
|
||||
QuartzComponent,
|
||||
QuartzComponentProps,
|
||||
QuartzComponentConstructor,
|
||||
} from "@quartz-community/types";
|
||||
import { classNames } from "../util/lang";
|
||||
import { i18n } from "../i18n";
|
||||
import style from "./styles/example.scss";
|
||||
// @ts-ignore
|
||||
import script from "./scripts/example.inline.ts";
|
||||
|
||||
export interface ExampleComponentOptions {
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default ((opts?: ExampleComponentOptions) => {
|
||||
const { prefix = "", suffix = "", className = "example-component" } = opts ?? {};
|
||||
|
||||
const Component: QuartzComponent = (props: QuartzComponentProps) => {
|
||||
const frontmatter = props.fileData?.frontmatter as { title?: string } | undefined;
|
||||
const title = frontmatter?.title ?? "Untitled";
|
||||
const fullText = `${prefix}${title}${suffix}`;
|
||||
|
||||
return <div class={classNames(className)}>{fullText}</div>;
|
||||
};
|
||||
|
||||
Component.css = style;
|
||||
Component.afterDOMLoaded = script;
|
||||
|
||||
return Component;
|
||||
}) satisfies QuartzComponentConstructor;
|
||||
171
src/components/NoteProperties.tsx
Normal file
171
src/components/NoteProperties.tsx
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
import type {
|
||||
QuartzComponent,
|
||||
QuartzComponentProps,
|
||||
QuartzComponentConstructor,
|
||||
} from "@quartz-community/types";
|
||||
import { classNames } from "../util/lang";
|
||||
import { i18n } from "../i18n";
|
||||
import style from "./styles/noteProperties.scss";
|
||||
// @ts-ignore
|
||||
import script from "./scripts/noteProperties.inline.ts";
|
||||
|
||||
export interface NotePropertiesComponentOptions {
|
||||
/** Collapse the properties panel by default */
|
||||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
function renderValue(value: unknown): preact.JSX.Element | string {
|
||||
if (value === null || value === undefined) {
|
||||
return <span class="note-properties-empty">—</span>;
|
||||
}
|
||||
|
||||
if (typeof value === "boolean") {
|
||||
return (
|
||||
<span class={classNames("note-properties-boolean", value ? "is-true" : "is-false")}>
|
||||
<input type="checkbox" checked={value} disabled />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof value === "number") {
|
||||
return <span class="note-properties-number">{value}</span>;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
// Check for wikilinks
|
||||
const wikilinkMatch = value.match(/^\[\[([^\]|]+)(?:\|([^\]]+))?\]\]$/);
|
||||
if (wikilinkMatch) {
|
||||
const target = wikilinkMatch[1];
|
||||
const display = wikilinkMatch[2] ?? target;
|
||||
return (
|
||||
<a href={target} class="internal note-properties-link">
|
||||
{display}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
// Check for markdown links
|
||||
const mdLinkMatch = value.match(/^\[([^\]]*)\]\(([^)]+)\)$/);
|
||||
if (mdLinkMatch) {
|
||||
const display = mdLinkMatch[1]!;
|
||||
const href = mdLinkMatch[2]!;
|
||||
const isExternal = href.startsWith("http://") || href.startsWith("https://");
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
class={classNames(isExternal ? "external" : "internal", "note-properties-link")}
|
||||
{...(isExternal ? { target: "_blank", rel: "noopener noreferrer" } : {})}
|
||||
>
|
||||
{display || href}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
// Check for URLs
|
||||
if (value.startsWith("http://") || value.startsWith("https://")) {
|
||||
return (
|
||||
<a
|
||||
href={value}
|
||||
class="external note-properties-link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{value}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return <span class="note-properties-text">{value}</span>;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return (
|
||||
<ul class="note-properties-list">
|
||||
{value.map((item, idx) => (
|
||||
<li key={idx}>{renderValue(item)}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof value === "object") {
|
||||
const entries = Object.entries(value as Record<string, unknown>);
|
||||
return (
|
||||
<dl class="note-properties-object">
|
||||
{entries.map(([k, v]) => (
|
||||
<>
|
||||
<dt>{k}</dt>
|
||||
<dd>{renderValue(v)}</dd>
|
||||
</>
|
||||
))}
|
||||
</dl>
|
||||
);
|
||||
}
|
||||
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function renderTagList(tags: string[]): preact.JSX.Element {
|
||||
return (
|
||||
<ul class="note-properties-tags">
|
||||
{tags.map((tag) => (
|
||||
<li key={tag}>
|
||||
<a href={`/tags/${tag}`} class="internal tag-link">
|
||||
#{tag}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export default ((opts?: NotePropertiesComponentOptions) => {
|
||||
const { collapsed = false } = opts ?? {};
|
||||
|
||||
const Component: QuartzComponent = (props: QuartzComponentProps) => {
|
||||
const noteProps = props.fileData?.noteProperties as
|
||||
| { properties: Record<string, unknown>; hideView: boolean }
|
||||
| undefined;
|
||||
|
||||
if (!noteProps || noteProps.hideView) return null;
|
||||
|
||||
const properties = noteProps.properties;
|
||||
const entries = Object.entries(properties);
|
||||
if (entries.length === 0) return null;
|
||||
|
||||
const locale = props.cfg?.locale || "en-US";
|
||||
const i18nData = i18n(locale);
|
||||
|
||||
return (
|
||||
<details
|
||||
class={classNames(props.displayClass, "note-properties")}
|
||||
open={!collapsed}
|
||||
data-collapsed={collapsed}
|
||||
>
|
||||
<summary class="note-properties-header">
|
||||
<span class="note-properties-title">{i18nData.components.noteProperties.title}</span>
|
||||
<span class="note-properties-count">{entries.length}</span>
|
||||
</summary>
|
||||
<table class="note-properties-table">
|
||||
<tbody>
|
||||
{entries.map(([key, value]) => (
|
||||
<tr key={key} class="note-properties-row">
|
||||
<td class="note-properties-key">{key}</td>
|
||||
<td class="note-properties-value">
|
||||
{key === "tags" && Array.isArray(value)
|
||||
? renderTagList(value as string[])
|
||||
: renderValue(value)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</details>
|
||||
);
|
||||
};
|
||||
|
||||
Component.css = style;
|
||||
Component.afterDOMLoaded = script;
|
||||
|
||||
return Component;
|
||||
}) satisfies QuartzComponentConstructor;
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
export { default as ExampleComponent } from "./ExampleComponent";
|
||||
export type { ExampleComponentOptions } from "./ExampleComponent";
|
||||
export { default as NotePropertiesComponent } from "./NoteProperties";
|
||||
export type { NotePropertiesComponentOptions } from "./NoteProperties";
|
||||
|
|
|
|||
|
|
@ -1,107 +0,0 @@
|
|||
// @ts-nocheck
|
||||
// ============================================================================
|
||||
// Example Inline Script for Quartz Community Plugin
|
||||
// ============================================================================
|
||||
// This file demonstrates patterns commonly used in Quartz plugin client-side code.
|
||||
// It is bundled as a string and injected via Component.afterDOMLoaded.
|
||||
//
|
||||
// Key patterns demonstrated:
|
||||
// 1. Listening to Quartz navigation events ('nav', 'prenav')
|
||||
// 2. Fetching content index data
|
||||
// 3. DOM manipulation with cleanup
|
||||
// 4. State persistence (localStorage/sessionStorage)
|
||||
// 5. Keyboard shortcut handling
|
||||
// 6. Proper event listener cleanup
|
||||
// ============================================================================
|
||||
|
||||
// Helper: Remove all children from an element
|
||||
function removeAllChildren(element) {
|
||||
while (element.firstChild) {
|
||||
element.removeChild(element.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: Simplify slug by removing trailing /index
|
||||
function simplifySlug(slug) {
|
||||
if (slug.endsWith("/index")) {
|
||||
return slug.slice(0, -6);
|
||||
}
|
||||
return slug;
|
||||
}
|
||||
|
||||
// Helper: Get current page slug from URL
|
||||
function getCurrentSlug() {
|
||||
let slug = window.location.pathname;
|
||||
if (slug.startsWith("/")) slug = slug.slice(1);
|
||||
if (slug.endsWith("/")) slug = slug.slice(0, -1);
|
||||
return slug || "index";
|
||||
}
|
||||
|
||||
// Helper: Fetch content index (commonly needed for search, graph, explorer)
|
||||
async function fetchContentIndex() {
|
||||
try {
|
||||
const response = await fetch("/static/contentIndex.json");
|
||||
const data = await response.json();
|
||||
// Handle both formats: { "slug": {...} } or { "content": { "slug": {...} } }
|
||||
return data.content || data;
|
||||
} catch (error) {
|
||||
console.error("[Plugin] Error fetching content index:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Main initialization function
|
||||
function init() {
|
||||
const components = document.querySelectorAll(".example-component");
|
||||
if (components.length === 0) return;
|
||||
|
||||
// Example: Track cleanup functions for event listeners
|
||||
const cleanupFns = [];
|
||||
|
||||
// Example: Add a keyboard shortcut (Ctrl/Cmd + Shift + E)
|
||||
function keyboardHandler(e) {
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === "e") {
|
||||
e.preventDefault();
|
||||
console.log("[ExampleComponent] Keyboard shortcut triggered!");
|
||||
// Do something interesting here
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", keyboardHandler);
|
||||
cleanupFns.push(() => document.removeEventListener("keydown", keyboardHandler));
|
||||
|
||||
// Example: Click handler with proper cleanup
|
||||
for (const component of components) {
|
||||
const clickHandler = () => {
|
||||
console.log("[ExampleComponent] Clicked!");
|
||||
};
|
||||
component.addEventListener("click", clickHandler);
|
||||
cleanupFns.push(() => component.removeEventListener("click", clickHandler));
|
||||
}
|
||||
|
||||
// Register cleanup with Quartz's cleanup system
|
||||
if (typeof window !== "undefined" && window.addCleanup) {
|
||||
window.addCleanup(() => {
|
||||
cleanupFns.forEach((fn) => fn());
|
||||
});
|
||||
}
|
||||
|
||||
console.log("[ExampleComponent] Initialized with", components.length, "component(s)");
|
||||
}
|
||||
|
||||
// Listen to Quartz navigation events
|
||||
// 'nav' fires after page navigation (including initial load)
|
||||
document.addEventListener("nav", (e) => {
|
||||
const slug = e.detail?.url || getCurrentSlug();
|
||||
console.log("[ExampleComponent] Navigation to:", slug);
|
||||
init();
|
||||
});
|
||||
|
||||
// 'prenav' fires before navigation - use for saving state
|
||||
document.addEventListener("prenav", () => {
|
||||
// Example: Save scroll position before navigation
|
||||
const component = document.querySelector(".example-component");
|
||||
if (component) {
|
||||
sessionStorage.setItem("exampleScrollTop", component.scrollTop?.toString() || "0");
|
||||
}
|
||||
});
|
||||
33
src/components/scripts/noteProperties.inline.ts
Normal file
33
src/components/scripts/noteProperties.inline.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// @ts-nocheck
|
||||
|
||||
|
||||
const STORAGE_KEY = "note-properties-collapsed";
|
||||
|
||||
function init() {
|
||||
const details = document.querySelector("details.note-properties");
|
||||
if (!details) return;
|
||||
|
||||
|
||||
const saved = localStorage.getItem(STORAGE_KEY);
|
||||
if (saved !== null) {
|
||||
const isCollapsed = saved === "true";
|
||||
details.open = !isCollapsed;
|
||||
}
|
||||
|
||||
|
||||
function toggleHandler() {
|
||||
localStorage.setItem(STORAGE_KEY, String(!details.open));
|
||||
}
|
||||
|
||||
details.addEventListener("toggle", toggleHandler);
|
||||
|
||||
if (typeof window !== "undefined" && window.addCleanup) {
|
||||
window.addCleanup(() => {
|
||||
details.removeEventListener("toggle", toggleHandler);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("nav", () => {
|
||||
init();
|
||||
});
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
.example-component {
|
||||
padding: 8px 16px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
}
|
||||
165
src/components/styles/noteProperties.scss
Normal file
165
src/components/styles/noteProperties.scss
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
.note-properties {
|
||||
margin: 0.5rem 0 1rem;
|
||||
border: 1px solid var(--lightgray);
|
||||
border-radius: 5px;
|
||||
font-size: 0.9rem;
|
||||
|
||||
&[open] > .note-properties-header {
|
||||
border-bottom: 1px solid var(--lightgray);
|
||||
}
|
||||
|
||||
.note-properties-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.4rem 0.8rem;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
list-style: none;
|
||||
color: var(--darkgray);
|
||||
font-weight: 600;
|
||||
|
||||
&::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 0.5em;
|
||||
height: 0.5em;
|
||||
border-right: 2px solid var(--darkgray);
|
||||
border-bottom: 2px solid var(--darkgray);
|
||||
transform: rotate(-45deg);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
&[open] > .note-properties-header::before {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.note-properties-count {
|
||||
margin-left: auto;
|
||||
font-size: 0.75rem;
|
||||
color: var(--gray);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.note-properties-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.note-properties-row {
|
||||
border-bottom: 1px solid var(--lightgray);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.note-properties-key {
|
||||
width: 35%;
|
||||
padding: 0.35rem 0.8rem;
|
||||
color: var(--gray);
|
||||
font-size: 0.85rem;
|
||||
vertical-align: top;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.note-properties-value {
|
||||
padding: 0.35rem 0.8rem;
|
||||
vertical-align: top;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.note-properties-empty {
|
||||
color: var(--gray);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.note-properties-boolean {
|
||||
input[type="checkbox"] {
|
||||
pointer-events: none;
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.note-properties-number {
|
||||
font-family: var(--codeFont);
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.note-properties-link {
|
||||
text-decoration: none;
|
||||
color: var(--secondary);
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.note-properties-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem 0.5rem;
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.note-properties-tags {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem 0.5rem;
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tag-link {
|
||||
display: inline-block;
|
||||
padding: 0.1rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
background: var(--highlight);
|
||||
color: var(--secondary);
|
||||
font-size: 0.85em;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background: var(--secondary);
|
||||
color: var(--light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.note-properties-object {
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0.15rem 0.5rem;
|
||||
|
||||
dt {
|
||||
color: var(--gray);
|
||||
font-size: 0.85em;
|
||||
|
||||
&::after {
|
||||
content: ":";
|
||||
}
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import type {
|
||||
QuartzEmitterPlugin,
|
||||
ProcessedContent,
|
||||
BuildCtx,
|
||||
FilePath,
|
||||
FullSlug,
|
||||
} from "@quartz-community/types";
|
||||
import type { ExampleEmitterOptions } from "./types";
|
||||
|
||||
const defaultOptions: ExampleEmitterOptions = {
|
||||
manifestSlug: "plugin-manifest",
|
||||
includeFrontmatter: true,
|
||||
metadata: {
|
||||
generator: "Quartz Plugin Template",
|
||||
},
|
||||
};
|
||||
|
||||
const joinSegments = (...segments: string[]) =>
|
||||
segments
|
||||
.filter((segment) => segment.length > 0)
|
||||
.join("/")
|
||||
.replace(/\/+/g, "/") as FilePath;
|
||||
|
||||
const writeFile = async (
|
||||
outputDir: string,
|
||||
slug: FullSlug,
|
||||
ext: `.${string}` | "",
|
||||
content: string,
|
||||
) => {
|
||||
const outputPath = joinSegments(outputDir, `${slug}${ext}`) as FilePath;
|
||||
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
||||
await fs.writeFile(outputPath, content);
|
||||
return outputPath;
|
||||
};
|
||||
|
||||
/**
|
||||
* Example emitter that writes a JSON manifest of content metadata.
|
||||
*/
|
||||
export const ExampleEmitter: QuartzEmitterPlugin<Partial<ExampleEmitterOptions>> = (
|
||||
userOptions?: Partial<ExampleEmitterOptions>,
|
||||
) => {
|
||||
const options = { ...defaultOptions, ...userOptions };
|
||||
const emitManifest = async (ctx: BuildCtx, content: ProcessedContent[]) => {
|
||||
const manifest = {
|
||||
...options.metadata,
|
||||
generatedAt: new Date().toISOString(),
|
||||
pages: content.map(([_tree, vfile]) => {
|
||||
const frontmatter = (vfile.data?.frontmatter ?? {}) as {
|
||||
title?: string;
|
||||
tags?: string[];
|
||||
[key: string]: unknown;
|
||||
};
|
||||
return {
|
||||
slug: vfile.data?.slug ?? null,
|
||||
title: frontmatter.title ?? null,
|
||||
tags: frontmatter.tags ?? null,
|
||||
filePath: vfile.data?.filePath ?? null,
|
||||
frontmatter: options.includeFrontmatter ? frontmatter : undefined,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
let json = `${JSON.stringify(manifest, null, 2)}\n`;
|
||||
if (options.transformManifest) {
|
||||
json = options.transformManifest(json);
|
||||
}
|
||||
|
||||
const output = await writeFile(
|
||||
ctx.argv.output,
|
||||
options.manifestSlug as FullSlug,
|
||||
".json",
|
||||
json,
|
||||
);
|
||||
return [output];
|
||||
};
|
||||
|
||||
return {
|
||||
name: "ExampleEmitter",
|
||||
async emit(ctx, content, _resources) {
|
||||
return emitManifest(ctx, content);
|
||||
},
|
||||
async *partialEmit(ctx, content, _resources, _changeEvents) {
|
||||
const outputPaths = await emitManifest(ctx, content);
|
||||
for (const outputPath of outputPaths) {
|
||||
yield outputPath;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
import type { QuartzFilterPlugin, ProcessedContent, BuildCtx } from "@quartz-community/types";
|
||||
import type { ExampleFilterOptions } from "./types";
|
||||
|
||||
const defaultOptions: ExampleFilterOptions = {
|
||||
allowDrafts: false,
|
||||
excludeTags: ["private"],
|
||||
excludePathPrefixes: ["_drafts/", "_private/"],
|
||||
};
|
||||
|
||||
const normalizeTag = (tag: unknown) => (typeof tag === "string" ? tag.trim().toLowerCase() : "");
|
||||
|
||||
const includesTag = (tags: unknown, excludedTags: string[]) => {
|
||||
if (!Array.isArray(tags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const normalizedExcluded = excludedTags.map((tag) => tag.toLowerCase());
|
||||
return tags.some((tag) => normalizedExcluded.includes(normalizeTag(tag)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Example filter that removes drafts, tagged pages, and excluded path prefixes.
|
||||
*/
|
||||
export const ExampleFilter: QuartzFilterPlugin<Partial<ExampleFilterOptions>> = (
|
||||
userOptions?: Partial<ExampleFilterOptions>,
|
||||
) => {
|
||||
const options = { ...defaultOptions, ...userOptions };
|
||||
return {
|
||||
name: "ExampleFilter",
|
||||
shouldPublish(_ctx: BuildCtx, [_tree, vfile]: ProcessedContent) {
|
||||
const frontmatter = (vfile.data?.frontmatter ?? {}) as {
|
||||
draft?: boolean | string;
|
||||
tags?: string[];
|
||||
};
|
||||
const isDraft = frontmatter.draft === true || frontmatter.draft === "true";
|
||||
if (isDraft && !options.allowDrafts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (includesTag(frontmatter.tags, options.excludeTags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const filePath = typeof vfile.data?.filePath === "string" ? vfile.data.filePath : "";
|
||||
const normalizedPath = filePath.replace(/\\/g, "/");
|
||||
if (options.excludePathPrefixes.some((prefix) => normalizedPath.startsWith(prefix))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
export default {
|
||||
components: {
|
||||
example: {
|
||||
title: "Example",
|
||||
noteProperties: {
|
||||
title: "Properties",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
24
src/index.ts
24
src/index.ts
|
|
@ -1,28 +1,12 @@
|
|||
export { ExampleTransformer } from "./transformer";
|
||||
export { ExampleFilter } from "./filter";
|
||||
export { ExampleEmitter } from "./emitter";
|
||||
export { default as ExampleComponent } from "./components/ExampleComponent";
|
||||
export { NoteProperties } from "./transformer";
|
||||
export { default as NotePropertiesComponent } from "./components/NoteProperties";
|
||||
|
||||
export type {
|
||||
ExampleTransformerOptions,
|
||||
ExampleFilterOptions,
|
||||
ExampleEmitterOptions,
|
||||
} from "./types";
|
||||
export type { NotePropertiesOptions } from "./types";
|
||||
export type { NotePropertiesComponentOptions } from "./components/NoteProperties";
|
||||
|
||||
export type { ExampleComponentOptions } from "./components/ExampleComponent";
|
||||
|
||||
// Re-export shared types from @quartz-community/types
|
||||
export type {
|
||||
QuartzComponent,
|
||||
QuartzComponentProps,
|
||||
QuartzComponentConstructor,
|
||||
StringResource,
|
||||
QuartzTransformerPlugin,
|
||||
QuartzFilterPlugin,
|
||||
QuartzEmitterPlugin,
|
||||
QuartzPageTypePlugin,
|
||||
QuartzPageTypePluginInstance,
|
||||
PageMatcher,
|
||||
PageGenerator,
|
||||
VirtualPage,
|
||||
} from "@quartz-community/types";
|
||||
|
|
|
|||
|
|
@ -1,103 +1,256 @@
|
|||
import type { PluggableList, Plugin } from "unified";
|
||||
import type { Root as MdastRoot } from "mdast";
|
||||
import type { Root as HastRoot, Element } from "hast";
|
||||
import type { VFile } from "vfile";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import rehypeSlug from "rehype-slug";
|
||||
import { findAndReplace } from "mdast-util-find-and-replace";
|
||||
import { visit } from "unist-util-visit";
|
||||
import type { QuartzTransformerPlugin, BuildCtx } from "@quartz-community/types";
|
||||
import type { ExampleTransformerOptions } from "./types";
|
||||
import matter from "gray-matter";
|
||||
import remarkFrontmatter from "remark-frontmatter";
|
||||
import yaml from "js-yaml";
|
||||
import toml from "toml";
|
||||
import type { PluggableList } from "unified";
|
||||
import type {
|
||||
QuartzTransformerPlugin,
|
||||
BuildCtx,
|
||||
QuartzPluginData,
|
||||
FullSlug,
|
||||
FilePath,
|
||||
} from "@quartz-community/types";
|
||||
import type { NotePropertiesOptions } from "./types";
|
||||
|
||||
const defaultOptions: ExampleTransformerOptions = {
|
||||
highlightToken: "==",
|
||||
headingClass: "example-plugin-heading",
|
||||
enableGfm: true,
|
||||
addHeadingSlugs: true,
|
||||
const defaultOptions: NotePropertiesOptions = {
|
||||
includeAll: false,
|
||||
includedProperties: ["description", "tags", "aliases"],
|
||||
excludedProperties: [],
|
||||
hidePropertiesView: false,
|
||||
delimiters: "---",
|
||||
language: "yaml",
|
||||
};
|
||||
|
||||
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
function coalesceAliases(data: Record<string, unknown>, aliases: string[]): unknown | undefined {
|
||||
for (const alias of aliases) {
|
||||
if (data[alias] !== undefined && data[alias] !== null) return data[alias];
|
||||
}
|
||||
}
|
||||
|
||||
const remarkHighlightToken = (token: string): Plugin<[], MdastRoot> => {
|
||||
const escapedToken = escapeRegExp(token);
|
||||
const pattern = new RegExp(`${escapedToken}([^\n]+?)${escapedToken}`, "g");
|
||||
return () => (tree: MdastRoot, _file: VFile) => {
|
||||
findAndReplace(tree, [
|
||||
[
|
||||
pattern,
|
||||
(_match: string, value: string) => ({
|
||||
type: "strong",
|
||||
children: [{ type: "text", value }],
|
||||
}),
|
||||
],
|
||||
]);
|
||||
};
|
||||
};
|
||||
function coerceToArray(input: unknown): string[] | undefined {
|
||||
if (input === undefined || input === null) return undefined;
|
||||
|
||||
const rehypeHeadingClass = (className: string): Plugin<[], HastRoot> => {
|
||||
return () => (tree: HastRoot, _file: VFile) => {
|
||||
visit(tree, "element", (node: Element) => {
|
||||
if (!/^h[1-6]$/.test(node.tagName)) {
|
||||
return;
|
||||
if (!Array.isArray(input)) {
|
||||
return String(input)
|
||||
.split(",")
|
||||
.map((s: string) => s.trim());
|
||||
}
|
||||
|
||||
return input
|
||||
.filter((v: unknown) => typeof v === "string" || typeof v === "number")
|
||||
.map((v: string | number) => v.toString());
|
||||
}
|
||||
|
||||
function slugTag(tag: string): string {
|
||||
return tag
|
||||
.split("/")
|
||||
.map((segment) =>
|
||||
segment
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/[^\w\p{L}\p{M}\p{N}/-]/gu, "")
|
||||
.toLowerCase(),
|
||||
)
|
||||
.join("/");
|
||||
}
|
||||
|
||||
function getFileExtension(fp: string): string {
|
||||
return fp.split(".").pop() ?? "";
|
||||
}
|
||||
|
||||
function slugifyFilePath(fp: string): FullSlug {
|
||||
fp = fp.replace(/\\/g, "/");
|
||||
fp = fp.replace(/\.md$/, "");
|
||||
let slug = fp
|
||||
.split("/")
|
||||
.map((segment) => segment.replace(/\s+/g, "-").replace(/[^\w\p{L}\p{M}\p{N}/-]/gu, ""))
|
||||
.join("/");
|
||||
slug = slug.replace(/\/$/, "");
|
||||
return slug as FullSlug;
|
||||
}
|
||||
|
||||
function getAliasSlugs(aliases: string[]): FullSlug[] {
|
||||
return aliases.map((alias) => {
|
||||
const isMd = getFileExtension(alias) === "md";
|
||||
const mockFp = isMd ? alias : alias + ".md";
|
||||
return slugifyFilePath(mockFp as FilePath);
|
||||
});
|
||||
}
|
||||
|
||||
// Wikilink pattern: [[target|display]] or [[target]]
|
||||
const WIKILINK_PATTERN = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g;
|
||||
// Markdown link pattern: [text](target)
|
||||
const MDLINK_PATTERN = /\[(?:[^\]]*)\]\(([^)]+)\)/g;
|
||||
|
||||
function extractLinksFromValue(value: unknown): string[] {
|
||||
if (typeof value === "string") {
|
||||
const links: string[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
WIKILINK_PATTERN.lastIndex = 0;
|
||||
while ((match = WIKILINK_PATTERN.exec(value)) !== null) {
|
||||
links.push(match[1]!);
|
||||
}
|
||||
|
||||
MDLINK_PATTERN.lastIndex = 0;
|
||||
while ((match = MDLINK_PATTERN.exec(value)) !== null) {
|
||||
links.push(match[1]!);
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.flatMap((item) => extractLinksFromValue(item));
|
||||
}
|
||||
|
||||
if (value !== null && typeof value === "object") {
|
||||
return Object.values(value).flatMap((v) => extractLinksFromValue(v));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function getVisibleProperties(
|
||||
data: Record<string, unknown>,
|
||||
opts: NotePropertiesOptions,
|
||||
): Record<string, unknown> {
|
||||
const excluded = new Set(opts.excludedProperties);
|
||||
|
||||
if (opts.includeAll) {
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
if (!excluded.has(key)) {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const existing = node.properties?.className;
|
||||
const classes: string[] = Array.isArray(existing)
|
||||
? existing.filter((value): value is string => typeof value === "string")
|
||||
: typeof existing === "string"
|
||||
? [existing]
|
||||
: [];
|
||||
node.properties = {
|
||||
...node.properties,
|
||||
className: [...classes, className],
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const key of opts.includedProperties) {
|
||||
if (!excluded.has(key) && data[key] !== undefined) {
|
||||
result[key] = data[key];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Example transformer showing remark/rehype usage and resource injection.
|
||||
*/
|
||||
export const ExampleTransformer: QuartzTransformerPlugin<Partial<ExampleTransformerOptions>> = (
|
||||
userOptions?: Partial<ExampleTransformerOptions>,
|
||||
export const NoteProperties: QuartzTransformerPlugin<Partial<NotePropertiesOptions>> = (
|
||||
userOpts,
|
||||
) => {
|
||||
const options = { ...defaultOptions, ...userOptions };
|
||||
const opts = { ...defaultOptions, ...userOpts };
|
||||
return {
|
||||
name: "ExampleTransformer",
|
||||
textTransform(_ctx: BuildCtx, src: string) {
|
||||
return src.endsWith("\n") ? src : `${src}\n`;
|
||||
},
|
||||
markdownPlugins(): PluggableList {
|
||||
const plugins: PluggableList = [remarkHighlightToken(options.highlightToken)];
|
||||
if (options.enableGfm) {
|
||||
plugins.unshift(remarkGfm);
|
||||
}
|
||||
return plugins;
|
||||
},
|
||||
htmlPlugins(): PluggableList {
|
||||
const plugins: PluggableList = [rehypeHeadingClass(options.headingClass)];
|
||||
if (options.addHeadingSlugs) {
|
||||
plugins.unshift(rehypeSlug);
|
||||
}
|
||||
return plugins;
|
||||
},
|
||||
externalResources() {
|
||||
return {
|
||||
css: [
|
||||
{
|
||||
content: `.${options.headingClass} { letter-spacing: 0.02em; }`,
|
||||
inline: true,
|
||||
},
|
||||
],
|
||||
js: [
|
||||
{
|
||||
contentType: "inline",
|
||||
loadTime: "afterDOMReady",
|
||||
script: "document.documentElement.dataset.exampleTransformer = 'true'",
|
||||
},
|
||||
],
|
||||
additionalHead: [],
|
||||
};
|
||||
name: "NoteProperties",
|
||||
markdownPlugins(ctx: BuildCtx) {
|
||||
const { cfg, allSlugs } = ctx;
|
||||
return [
|
||||
[remarkFrontmatter, ["yaml", "toml"]],
|
||||
() => {
|
||||
return (_, file) => {
|
||||
const fileData = Buffer.from(file.value as Uint8Array);
|
||||
const { data } = matter(fileData, {
|
||||
delimiters: opts.delimiters,
|
||||
language: opts.language,
|
||||
engines: {
|
||||
yaml: (s) => yaml.load(s, { schema: yaml.JSON_SCHEMA }) as object,
|
||||
toml: (s) => toml.parse(s) as object,
|
||||
},
|
||||
});
|
||||
|
||||
if (data.title != null && data.title.toString() !== "") {
|
||||
data.title = data.title.toString();
|
||||
} else {
|
||||
data.title = file.stem ?? "Untitled";
|
||||
}
|
||||
|
||||
const tags = coerceToArray(coalesceAliases(data, ["tags", "tag"]));
|
||||
if (tags) data.tags = [...new Set(tags.map((tag: string) => slugTag(tag)))];
|
||||
|
||||
const aliases = coerceToArray(coalesceAliases(data, ["aliases", "alias"]));
|
||||
if (aliases) {
|
||||
data.aliases = aliases;
|
||||
file.data.aliases = getAliasSlugs(aliases);
|
||||
allSlugs.push(...file.data.aliases);
|
||||
}
|
||||
|
||||
if (data.permalink != null && data.permalink.toString() !== "") {
|
||||
data.permalink = data.permalink.toString() as FullSlug;
|
||||
const fileAliases = (file.data.aliases as FullSlug[]) ?? [];
|
||||
fileAliases.push(data.permalink);
|
||||
file.data.aliases = fileAliases;
|
||||
allSlugs.push(data.permalink);
|
||||
}
|
||||
|
||||
const cssclasses = coerceToArray(coalesceAliases(data, ["cssclasses", "cssclass"]));
|
||||
if (cssclasses) data.cssclasses = cssclasses;
|
||||
|
||||
const socialImage = coalesceAliases(data, ["socialImage", "image", "cover"]);
|
||||
|
||||
const created = coalesceAliases(data, ["created", "date"]);
|
||||
if (created) data.created = created;
|
||||
|
||||
const modified = coalesceAliases(data, [
|
||||
"modified",
|
||||
"lastmod",
|
||||
"updated",
|
||||
"last-modified",
|
||||
]);
|
||||
if (modified) data.modified = modified;
|
||||
data.modified ||= created;
|
||||
|
||||
const published = coalesceAliases(data, ["published", "publishDate", "date"]);
|
||||
if (published) data.published = published;
|
||||
|
||||
if (socialImage) data.socialImage = socialImage;
|
||||
|
||||
const uniqueSlugs = [...new Set(allSlugs)];
|
||||
allSlugs.splice(0, allSlugs.length, ...uniqueSlugs);
|
||||
|
||||
const frontmatterLinks = extractLinksFromValue(data);
|
||||
if (frontmatterLinks.length > 0) {
|
||||
const existingLinks = (file.data.frontmatterLinks as string[]) ?? [];
|
||||
file.data.frontmatterLinks = [...existingLinks, ...frontmatterLinks];
|
||||
}
|
||||
|
||||
const visibleProps = getVisibleProperties(data, opts);
|
||||
file.data.noteProperties = {
|
||||
properties: visibleProps,
|
||||
hideView: opts.hidePropertiesView,
|
||||
};
|
||||
|
||||
file.data.frontmatter = data as QuartzPluginData["frontmatter"];
|
||||
};
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
declare module "vfile" {
|
||||
interface DataMap {
|
||||
aliases: FullSlug[];
|
||||
frontmatter: { [key: string]: unknown } & {
|
||||
title: string;
|
||||
} & Partial<{
|
||||
tags: string[];
|
||||
aliases: string[];
|
||||
modified: string;
|
||||
created: string;
|
||||
published: string;
|
||||
description: string;
|
||||
socialDescription: string;
|
||||
publish: boolean | string;
|
||||
draft: boolean | string;
|
||||
lang: string;
|
||||
enableToc: string;
|
||||
cssclasses: string[];
|
||||
socialImage: string;
|
||||
comments: boolean | string;
|
||||
}>;
|
||||
frontmatterLinks: string[];
|
||||
noteProperties: {
|
||||
properties: Record<string, unknown>;
|
||||
hideView: boolean;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
55
src/types.ts
55
src/types.ts
|
|
@ -17,46 +17,21 @@ export type {
|
|||
VirtualPage,
|
||||
QuartzPageTypePlugin,
|
||||
QuartzPageTypePluginInstance,
|
||||
FullSlug,
|
||||
FilePath,
|
||||
} from "@quartz-community/types";
|
||||
|
||||
export interface ExampleTransformerOptions {
|
||||
/** Token used to highlight text, defaults to ==highlight== */
|
||||
highlightToken: string;
|
||||
/** Add a CSS class to all headings in the rendered HTML. */
|
||||
headingClass: string;
|
||||
/** Enable remark-gfm for tables/task lists. */
|
||||
enableGfm: boolean;
|
||||
/** Enable adding slug IDs to headings. */
|
||||
addHeadingSlugs: boolean;
|
||||
}
|
||||
|
||||
export interface ExampleFilterOptions {
|
||||
/** Allow pages marked draft: true to publish. */
|
||||
allowDrafts: boolean;
|
||||
/** Exclude pages that contain any of these frontmatter tags. */
|
||||
excludeTags: string[];
|
||||
/** Exclude paths that start with any of these prefixes (relative to content root). */
|
||||
excludePathPrefixes: string[];
|
||||
}
|
||||
|
||||
export interface ExampleEmitterOptions {
|
||||
/** Filename to emit at the site root. */
|
||||
manifestSlug: string;
|
||||
/** Whether to include the frontmatter block in the manifest. */
|
||||
includeFrontmatter: boolean;
|
||||
/** Extra metadata to write at the top level of the manifest. */
|
||||
metadata: Record<string, unknown>;
|
||||
/** Optional hook to transform the emitted manifest JSON string. */
|
||||
transformManifest?: (json: string) => string;
|
||||
/** Add a custom class to the emitted manifest <script> tag if used in HTML. */
|
||||
manifestScriptClass?: string;
|
||||
}
|
||||
|
||||
export interface ExampleComponentOptions {
|
||||
/** Text to prefix before the title */
|
||||
prefix?: string;
|
||||
/** Text to suffix after the title */
|
||||
suffix?: string;
|
||||
/** CSS class name to apply */
|
||||
className?: string;
|
||||
export interface NotePropertiesOptions {
|
||||
/** Include all frontmatter properties in the display. When false, only `includedProperties` are shown. */
|
||||
includeAll: boolean;
|
||||
/** Properties to include when `includeAll` is false. Ignored when `includeAll` is true. */
|
||||
includedProperties: string[];
|
||||
/** Properties to exclude from display. Applied after inclusion logic. */
|
||||
excludedProperties: string[];
|
||||
/** Hide the visual properties panel while still processing frontmatter and resolving links. */
|
||||
hidePropertiesView: boolean;
|
||||
/** Frontmatter delimiters. Defaults to "---". */
|
||||
delimiters: string | [string, string];
|
||||
/** Frontmatter language. Defaults to "yaml". */
|
||||
language: "yaml" | "toml";
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue