diff --git a/.gitignore b/.gitignore index aee3beb..a4cb0e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /node_modules /public/generated +/public/index.html diff --git a/package-lock.json b/package-lock.json index 64d8c68..f914924 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "@babel/preset-react": "^7.16.7", "@babel/preset-typescript": "^7.26.0", "@babel/register": "^7.16.9", + "@microsoft/tsdoc": "^0.15.1", "@visionappscz/eslint-config-visionapps": "^1.5.0", "babel-loader": "^9.2.1", "babel-plugin-prismjs": "^2.1.0", @@ -38,9 +39,11 @@ "eslint-plugin-markdown": "^2.2.1", "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-hooks": "^4.3.0", + "glob": "^11.0.3", "jest": "^29.7.0", "style-loader": "^4.0.0", "terser-webpack-plugin": "^5.3.1", + "typescript": "^5.8.3", "uglify-js": "^3.15.5", "webpack": "^5.66.0", "webpack-cli": "^6.0.1", @@ -2098,6 +2101,125 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -2402,6 +2524,28 @@ } } }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -2630,6 +2774,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@microsoft/tsdoc": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", + "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==", + "dev": true, + "license": "MIT" + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -5130,6 +5281,13 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -6341,6 +6499,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -6521,22 +6709,24 @@ } }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", "dev": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "*" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -6562,6 +6752,22 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -7848,6 +8054,22 @@ "node": ">= 0.4" } }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -8002,6 +8224,28 @@ } } }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/jest-diff": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", @@ -8309,6 +8553,28 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/jest-snapshot": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", @@ -8977,6 +9243,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -9393,6 +9669,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -9496,6 +9779,33 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -10301,6 +10611,28 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-applescript": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", @@ -11029,6 +11361,29 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -11162,6 +11517,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -11362,6 +11731,28 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -11607,6 +11998,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", @@ -12248,6 +12653,25 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 9b890ad..22ed145 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@babel/preset-react": "^7.16.7", "@babel/preset-typescript": "^7.26.0", "@babel/register": "^7.16.9", + "@microsoft/tsdoc": "^0.15.1", "@visionappscz/eslint-config-visionapps": "^1.5.0", "babel-loader": "^9.2.1", "babel-plugin-prismjs": "^2.1.0", @@ -58,9 +59,11 @@ "eslint-plugin-markdown": "^2.2.1", "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-hooks": "^4.3.0", + "glob": "^11.0.3", "jest": "^29.7.0", "style-loader": "^4.0.0", "terser-webpack-plugin": "^5.3.1", + "typescript": "^5.8.3", "uglify-js": "^3.15.5", "webpack": "^5.66.0", "webpack-cli": "^6.0.1", diff --git a/public/exampleJS/functions.js b/public/exampleJS/functions.js new file mode 100644 index 0000000..0f020cd --- /dev/null +++ b/public/exampleJS/functions.js @@ -0,0 +1,11 @@ +/** + * Formats a user's full name + * @param {string} firstName - The user's first name + * @param {string} lastName - The user's last name + * @param {boolean} [includeTitle=false] - Whether to include a title + * @returns {string} The formatted full name + */ +export function formatName(firstName, lastName, includeTitle = false) { + const fullName = `${firstName} ${lastName}`; + return includeTitle ? `Mr./Ms. ${fullName}` : fullName; +} diff --git a/public/exampleTS/functions.ts b/public/exampleTS/functions.ts new file mode 100644 index 0000000..d7a959d --- /dev/null +++ b/public/exampleTS/functions.ts @@ -0,0 +1,11 @@ +/** + * Formats a user's full name with proper capitalization + * @param firstName - The user's first name + * @param lastName - The user's last name + * @param includeTitle - Whether to include a title prefix + * @returns The formatted full name + */ +export function formatName(firstName: string, lastName: string, includeTitle: boolean = false): string { + const fullName = `${firstName} ${lastName}`; + return includeTitle ? `Mr./Ms. ${fullName}` : fullName; +} \ No newline at end of file diff --git a/public/index.html b/public/index.html index e84706a..94ec894 100644 --- a/public/index.html +++ b/public/index.html @@ -236,7 +236,24 @@

Layout

- + +

docoff-function-doc

+ +

<docoff-function-doc> displays function documentation extracted from TSDoc comments in TypeScript files.

+ +

Usage

+ +

Pure HTML

+

+<docoff-function-doc src="/path/to/your/functions.ts:functionName"></docoff-function-doc>
+      
+ +

Example

+ +

Displaying documentation for example functions:

+ +
formatName
Formats a user's full name with proper capitalization
Parameter: firstName: string
The user's first name
Parameter: lastName: string
The user's last name
Parameter: includeTitle: boolean (optional)
Whether to include a title prefix
Returns: string
The formatted full name
+ diff --git a/public/index.template.html b/public/index.template.html new file mode 100644 index 0000000..b5997c7 --- /dev/null +++ b/public/index.template.html @@ -0,0 +1,260 @@ + + + + + + Docoff + + + + + + + + + + + + + +
+ +

Docoff Demo

+ +

Styles used in this demo:

+ + +

Feel free to inspect them in dev tools!

+ +

docoff-react-base

+ +

<docoff-react-base> allows to define code that is common for all docoff-react-preview elements on the + given page. This is handy for defining code fragments susch as constants or placeholder React components that + can be used further on.

+ +

Usage

+ +

Pure HTML

+

+<textarea is="docoff-react-base">
+// Your JS code here
+</textarea>
+      
+ +

Markdown

+

+```docoff-react-base
+// Your JS code here
+```
+      
+ +

Example

+ + + +

docoff-react-preview

+ +

<docoff-react-preview> renders a live preview of JSX code.

+ +

Usage

+ +

Pure HTML

+

+<textarea is="docoff-react-preview" css="/shadow.css">
+  <YourCustomJSX />
+</textarea>
+      
+ +

Markdown

+

+```docoff-react-base
+// Your JS code here
+```
+      
+ +

Example

+ +

Single Component

+ + +

Typescript

+ + +

Multiple Components

+ + +

With Hooks

+ + + + + +

docoff-react-props

+ +

<docoff-react-props> renders the component prop definition table.

+

For JavaScript, the props can be loaded from multiple files. This allows for writing more DRY code when several components share same props.

+

There are simple rules for loading props from multiple files:

+ + +

Examples

+

Using docoff-react-preview :)

+ +

Typescript

+
node_modules/.bin/react-docgen public/exampleTS/Greeting.tsx > Greeting.props.json
+ + + +

Relative URL with Overloading

+ +

⚠️ Does not work with TypeScript

+ + + +

Absolute URL

+ + + + + +

docoff-placeholder

+ + <docoff-placeholder> renders a placeholder that can be used in code examples. + + + +

Children

+

Children can be inserted

+ + +

Color

+

It can be rendered dark

+ + +

Size

+

Size can be set using any valid css units

+ + +

Layout

+

It can be rendered inline

+ + +

docoff-function-doc

+ +

<docoff-function-doc> displays function documentation extracted from TSDoc comments in TypeScript files.

+ +

Usage

+ +

Pure HTML

+

+<docoff-function-doc src="/path/to/your/functions.ts:functionName"></docoff-function-doc>
+      
+ +

Example

+ +

Displaying documentation for example functions:

+ + + +
+ + + diff --git a/scripts/processDocoffFunctionDoc.js b/scripts/processDocoffFunctionDoc.js new file mode 100644 index 0000000..4349c0e --- /dev/null +++ b/scripts/processDocoffFunctionDoc.js @@ -0,0 +1,262 @@ +const fs = require('fs'); +const path = require('path'); +const glob = require('glob'); +const { TSDocParser } = require('@microsoft/tsdoc'); +const ts = require('typescript'); + +/** + * Standalone function to process docoff-function-doc elements in HTML files + * and replace them with static HTML content + */ +async function processDocoffFunctionDoc(options = {}) { + const { + sourceDir = 'public', + outputDir = 'public', + htmlPattern = '**/*.html', + } = options; + + console.log('Processing docoff-function-doc elements...'); + + // Find all HTML files to process + const htmlFiles = glob.sync(path.join(sourceDir, htmlPattern)); + + for (const htmlFile of htmlFiles) { + console.log(`Processing ${htmlFile}...`); + + let content = fs.readFileSync(htmlFile, 'utf-8'); + let hasChanges = false; + + // Find all docoff-function-doc elements + const regex = /<\/docoff-function-doc>/g; + let match; + + while ((match = regex.exec(content)) !== null) { + const srcAttribute = match[1]; + const [filePath, functionName] = srcAttribute.split(':'); + + if (filePath && functionName) { + try { + const htmlContent = await generateFunctionDoc(filePath, functionName, path.dirname(htmlFile)); + content = content.replace(match[0], htmlContent); + hasChanges = true; + console.log(` Replaced docoff-function-doc for ${srcAttribute}`); + } catch (error) { + console.warn(` Warning: Failed to process docoff-function-doc for ${srcAttribute}: ${error.message}`); + // Replace with error message + const errorHtml = `
Error loading function documentation: ${error.message}
`; + content = content.replace(match[0], errorHtml); + hasChanges = true; + } + } + } + + if (hasChanges) { + // Calculate output path - remove .template from filename + let relativePath = path.relative(sourceDir, htmlFile); + relativePath = relativePath.replace('.template.html', '.html'); + const outputPath = path.resolve(outputDir, relativePath); + + // Ensure output directory exists + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + + // Write the updated content + fs.writeFileSync(outputPath, content, 'utf-8'); + console.log(` Updated ${outputPath}`); + } + } + + console.log('Finished processing docoff-function-doc elements.'); +} + +async function generateFunctionDoc(filePath, functionName, baseDir) { + // Resolve the absolute path to the TypeScript file + const fullPath = path.resolve(baseDir, filePath.replace(/^\//, '')); + + if (!fs.existsSync(fullPath)) { + throw new Error(`File not found: ${fullPath}`); + } + + const fileContent = fs.readFileSync(fullPath, 'utf-8'); + + // Parse TypeScript file + const sourceFile = ts.createSourceFile( + fullPath, + fileContent, + ts.ScriptTarget.Latest, + true + ); + + // Find the specific function + const functionNode = findFunctionNode(sourceFile, functionName); + + if (!functionNode) { + throw new Error(`Function '${functionName}' not found in ${filePath}`); + } + + // Extract TSDoc comment + const tsdocComment = extractTSDocComment(sourceFile, functionNode); + + if (!tsdocComment) { + throw new Error(`No TSDoc comment found for function '${functionName}'`); + } + + // Extract type information from the function + const typeInfo = extractTypeInformation(functionNode); + + // Parse TSDoc comment + const parser = new TSDocParser(); + const parserContext = parser.parseString(tsdocComment); + + if (parserContext.log.messages.length > 0) { + console.warn(`TSDoc parsing warnings for ${functionName}:`, parserContext.log.messages); + } + + // Generate HTML from parsed TSDoc + return generateHTMLFromTSDoc(functionName, parserContext.docComment, typeInfo); +} + +function findFunctionNode(sourceFile, functionName) { + let functionNode = null; + + const visit = (node) => { + if (ts.isFunctionDeclaration(node) && node.name && node.name.text === functionName) { + functionNode = node; + return; + } + + if (ts.isVariableStatement(node)) { + for (const declaration of node.declarationList.declarations) { + if (ts.isIdentifier(declaration.name) && declaration.name.text === functionName) { + if (declaration.initializer && + (ts.isFunctionExpression(declaration.initializer) || + ts.isArrowFunction(declaration.initializer))) { + functionNode = node; + return; + } + } + } + } + + ts.forEachChild(node, visit); + }; + + visit(sourceFile); + return functionNode; +} + +function extractTSDocComment(sourceFile, functionNode) { + // Get leading trivia (comments) for the function node + const leadingTrivia = functionNode.getFullText().substring(0, functionNode.getLeadingTriviaWidth()); + + // Look for TSDoc comment (/** ... */) + const tsdocRegex = /\/\*\*[\s\S]*?\*\//g; + const matches = leadingTrivia.match(tsdocRegex); + + if (matches && matches.length > 0) { + // Return the last (closest) TSDoc comment + return matches[matches.length - 1]; + } + + return null; +} + +function extractTypeInformation(functionNode) { + const typeInfo = { + parameters: [], + returnType: null + }; + + // Extract parameter types + if (functionNode.parameters) { + for (const param of functionNode.parameters) { + const paramName = param.name.text; + let paramType = 'any'; + + if (param.type) { + paramType = param.type.getText(); + } + + const isOptional = param.questionToken !== undefined || param.initializer !== undefined; + + typeInfo.parameters.push({ + name: paramName, + type: paramType, + optional: isOptional + }); + } + } + + // Extract return type + if (functionNode.type) { + typeInfo.returnType = functionNode.type.getText(); + } + + return typeInfo; +} + +function generateHTMLFromTSDoc(functionName, docComment, typeInfo) { + const summary = docComment.summarySection; + const params = docComment.params; + const returnsBlock = docComment.returnsBlock; + + let html = '
'; + + // Function name and description + html += `
${functionName}
`; + + if (summary && summary.nodes.length > 0) { + const description = extractTextFromNodes(summary.nodes); + html += `
${description}
`; + } + + // Parameters + if (params.blocks.length > 0) { + for (const param of params.blocks) { + const paramName = param.parameterName; + const paramDescription = param.content ? extractTextFromNodes(param.content.nodes) : ''; + + // Find type information for this parameter + const typeInfoParam = typeInfo.parameters.find(p => p.name === paramName); + const typeDisplay = typeInfoParam ? `${typeInfoParam.type}` : ''; + const optionalDisplay = typeInfoParam && typeInfoParam.optional ? ' (optional)' : ''; + + html += `
Parameter: ${paramName}${typeDisplay ? `: ${typeDisplay}` : ''}${optionalDisplay}
`; + html += `
${paramDescription}
`; + } + } + + // Returns + if (returnsBlock && returnsBlock.content) { + const returnDescription = extractTextFromNodes(returnsBlock.content.nodes); + const returnTypeDisplay = typeInfo.returnType ? `${typeInfo.returnType}` : ''; + html += `
Returns${returnTypeDisplay ? `: ${returnTypeDisplay}` : ''}
`; + html += `
${returnDescription}
`; + } + + html += '
'; + + return html; +} + +function extractTextFromNodes(nodes) { + return nodes.map(node => { + if (node.kind === 'PlainText') { + return node.text; + } else if (node.kind === 'Paragraph') { + return extractTextFromNodes(node.nodes); + } else if (node.kind === 'CodeSpan') { + return `${node.code}`; + } + return ''; + }).join('').trim(); +} + +module.exports = { + processDocoffFunctionDoc, + generateFunctionDoc, +}; + +// If called directly from command line +if (require.main === module) { + processDocoffFunctionDoc().catch(console.error); +} \ No newline at end of file diff --git a/src/DocoffFunctionDoc/DocoffFunctionDoc.js b/src/DocoffFunctionDoc/DocoffFunctionDoc.js new file mode 100644 index 0000000..c79a87f --- /dev/null +++ b/src/DocoffFunctionDoc/DocoffFunctionDoc.js @@ -0,0 +1,36 @@ +/** + * Custom element for displaying function documentation + * Shows a warning when not processed by the webpack plugin + */ +export class DocoffFunctionDoc extends HTMLElement { + static get observedAttributes() { + return ['src']; + } + + connectedCallback() { + this.render(); + } + + attributeChangedCallback() { + this.render(); + } + + get src() { + return this.getAttribute('src'); + } + + render() { + if (!this.src) { + this.innerHTML = '
Error: src attribute is required
'; + return; + } + + // If this element is still present, it means the webpack plugin didn't process it + this.innerHTML = ` +
+ Warning: This file needs to be processed through the build system first. +
Function documentation for ${this.src} will be generated during build. +
+ `; + } +} \ No newline at end of file diff --git a/src/DocoffFunctionDoc/index.js b/src/DocoffFunctionDoc/index.js new file mode 100644 index 0000000..c51e44b --- /dev/null +++ b/src/DocoffFunctionDoc/index.js @@ -0,0 +1 @@ +export { DocoffFunctionDoc } from './DocoffFunctionDoc'; \ No newline at end of file diff --git a/src/_plugins/DocoffFunctionDocPlugin.js b/src/_plugins/DocoffFunctionDocPlugin.js new file mode 100644 index 0000000..f459323 --- /dev/null +++ b/src/_plugins/DocoffFunctionDocPlugin.js @@ -0,0 +1,28 @@ +const { processDocoffFunctionDoc } = require('../../scripts/processDocoffFunctionDoc'); + +/** + * Webpack plugin that processes docoff-function-doc elements during build + */ +class DocoffFunctionDocPlugin { + constructor(options = {}) { + this.options = { + htmlPattern: options.htmlPattern || '**/*.html', + outputDir: options.outputDir || 'public', + sourceDir: options.sourceDir || 'public', + ...options, + }; + } + + apply(compiler) { + compiler.hooks.afterEmit.tapAsync('DocoffFunctionDocPlugin', async (compilation, callback) => { + try { + await processDocoffFunctionDoc(this.options); + callback(); + } catch (error) { + callback(error); + } + }); + } +} + +module.exports = DocoffFunctionDocPlugin; diff --git a/src/main.js b/src/main.js index 115d4b5..438d72f 100644 --- a/src/main.js +++ b/src/main.js @@ -2,11 +2,13 @@ import { DocoffPlaceholder } from './DocoffPlaceholder'; import { DocoffReactPreview } from './DocoffReactPreview'; import { DocoffReactBase } from './DocoffReactBase'; import { DocoffReactProps } from './DocoffReactProps'; +import { DocoffFunctionDoc } from './DocoffFunctionDoc'; customElements.define('docoff-react-preview', DocoffReactPreview, { extends: 'textarea' }); customElements.define('docoff-react-base', DocoffReactBase, { extends: 'textarea' }); customElements.define('docoff-react-props', DocoffReactProps); customElements.define('docoff-placeholder', DocoffPlaceholder); +customElements.define('docoff-function-doc', DocoffFunctionDoc); // For comfortable usage in Markdown any `` elements with class `language-docoff-*` // get replaced by the respective custom elements diff --git a/webpack.config.babel.js b/webpack.config.babel.js index 6b87d7f..20bbb59 100644 --- a/webpack.config.babel.js +++ b/webpack.config.babel.js @@ -1,6 +1,7 @@ const Path = require('path'); const TerserPlugin = require('terser-webpack-plugin'); const webpack = require('webpack'); +const DocoffFunctionDocPlugin = require('./src/_plugins/DocoffFunctionDocPlugin'); const MAX_OUTPUT_SIZE_KB = 1600000; @@ -51,6 +52,9 @@ module.exports = (env, argv) => ({ publicPath: '/generated/', }, plugins: [ + new DocoffFunctionDocPlugin({ + htmlPattern: '**/*.template.html' + }), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(argv.mode), 'process.env.BABEL_ENV': JSON.stringify(argv.mode),