diff --git a/package-lock.json b/package-lock.json index c3f1f61..2828829 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "portfolio", - "version": "1.0.2", + "version": "1.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "portfolio", - "version": "1.0.2", + "version": "1.0.3", "dependencies": { "@adobe/react-spectrum": "^3.45.0", "@mdx-js/mdx": "^3.1.1", @@ -16,7 +16,6 @@ "@spectrum-icons/workflow": "^4.2.25", "framer-motion": "^12.23.24", "lucide-react": "^0.555.0", - "mermaid": "^11.12.1", "react": "^19.2.0", "react-aria-components": "^1.13.0", "react-dom": "^19.2.0", @@ -162,28 +161,6 @@ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" } }, - "node_modules/@antfu/install-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", - "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", - "license": "MIT", - "dependencies": { - "package-manager-detector": "^1.3.0", - "tinyexec": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@antfu/install-pkg/node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/@asamuzakjp/css-color": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.0.tgz", @@ -561,51 +538,6 @@ "node": ">=18" } }, - "node_modules/@braintree/sanitize-url": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", - "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==", - "license": "MIT" - }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", - "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "11.0.3", - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/@chevrotain/gast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", - "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/@chevrotain/regexp-to-ast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", - "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/types": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", - "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/utils": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", - "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", - "license": "Apache-2.0" - }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -1037,23 +969,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@iconify/types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "license": "MIT" - }, - "node_modules/@iconify/utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz", - "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", - "license": "MIT", - "dependencies": { - "@antfu/install-pkg": "^1.1.0", - "@iconify/types": "^2.0.0", - "mlly": "^1.8.0" - } - }, "node_modules/@internationalized/date": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz", @@ -1195,15 +1110,6 @@ "react": ">=16" } }, - "node_modules/@mermaid-js/parser": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", - "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", - "license": "MIT", - "dependencies": { - "langium": "3.3.1" - } - }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", @@ -5041,259 +4947,6 @@ "assertion-error": "^2.0.1" } }, - "node_modules/@types/d3": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", - "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", - "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/d3-axis": "*", - "@types/d3-brush": "*", - "@types/d3-chord": "*", - "@types/d3-color": "*", - "@types/d3-contour": "*", - "@types/d3-delaunay": "*", - "@types/d3-dispatch": "*", - "@types/d3-drag": "*", - "@types/d3-dsv": "*", - "@types/d3-ease": "*", - "@types/d3-fetch": "*", - "@types/d3-force": "*", - "@types/d3-format": "*", - "@types/d3-geo": "*", - "@types/d3-hierarchy": "*", - "@types/d3-interpolate": "*", - "@types/d3-path": "*", - "@types/d3-polygon": "*", - "@types/d3-quadtree": "*", - "@types/d3-random": "*", - "@types/d3-scale": "*", - "@types/d3-scale-chromatic": "*", - "@types/d3-selection": "*", - "@types/d3-shape": "*", - "@types/d3-time": "*", - "@types/d3-time-format": "*", - "@types/d3-timer": "*", - "@types/d3-transition": "*", - "@types/d3-zoom": "*" - } - }, - "node_modules/@types/d3-array": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "license": "MIT" - }, - "node_modules/@types/d3-axis": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", - "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-brush": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", - "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-chord": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", - "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", - "license": "MIT" - }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" - }, - "node_modules/@types/d3-contour": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", - "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", - "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", - "license": "MIT" - }, - "node_modules/@types/d3-dispatch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", - "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", - "license": "MIT" - }, - "node_modules/@types/d3-drag": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", - "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-dsv": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", - "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", - "license": "MIT" - }, - "node_modules/@types/d3-ease": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "license": "MIT" - }, - "node_modules/@types/d3-fetch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", - "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", - "license": "MIT", - "dependencies": { - "@types/d3-dsv": "*" - } - }, - "node_modules/@types/d3-force": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", - "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", - "license": "MIT" - }, - "node_modules/@types/d3-format": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", - "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", - "license": "MIT" - }, - "node_modules/@types/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", - "license": "MIT", - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-hierarchy": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", - "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", - "license": "MIT" - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "license": "MIT", - "dependencies": { - "@types/d3-color": "*" - } - }, - "node_modules/@types/d3-path": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "license": "MIT" - }, - "node_modules/@types/d3-polygon": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", - "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", - "license": "MIT" - }, - "node_modules/@types/d3-quadtree": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", - "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", - "license": "MIT" - }, - "node_modules/@types/d3-random": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", - "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", - "license": "MIT" - }, - "node_modules/@types/d3-scale": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", - "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", - "license": "MIT", - "dependencies": { - "@types/d3-time": "*" - } - }, - "node_modules/@types/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", - "license": "MIT" - }, - "node_modules/@types/d3-selection": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", - "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", - "license": "MIT" - }, - "node_modules/@types/d3-shape": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", - "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", - "license": "MIT", - "dependencies": { - "@types/d3-path": "*" - } - }, - "node_modules/@types/d3-time": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", - "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "license": "MIT" - }, - "node_modules/@types/d3-time-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", - "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", - "license": "MIT" - }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "license": "MIT" - }, - "node_modules/@types/d3-transition": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", - "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-zoom": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", - "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", - "license": "MIT", - "dependencies": { - "@types/d3-interpolate": "*", - "@types/d3-selection": "*" - } - }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -5325,12 +4978,6 @@ "@types/estree": "*" } }, - "node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "license": "MIT" - }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -5407,13 +5054,6 @@ "@types/react": "^19.2.0" } }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT", - "optional": true - }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -6737,33 +6377,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/chevrotain": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", - "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@chevrotain/cst-dts-gen": "11.0.3", - "@chevrotain/gast": "11.0.3", - "@chevrotain/regexp-to-ast": "11.0.3", - "@chevrotain/types": "11.0.3", - "@chevrotain/utils": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/chevrotain-allstar": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", - "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", - "license": "MIT", - "dependencies": { - "lodash-es": "^4.17.21" - }, - "peerDependencies": { - "chevrotain": "^11.0.0" - } - }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -6819,15 +6432,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6835,12 +6439,6 @@ "dev": true, "license": "MIT" }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "license": "MIT" - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -6857,15 +6455,6 @@ "node": ">=18" } }, - "node_modules/cose-base": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", - "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", - "license": "MIT", - "dependencies": { - "layout-base": "^1.0.0" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -6923,595 +6512,88 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, - "node_modules/cytoscape": { - "version": "3.33.1", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", - "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10" - } + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" }, - "node_modules/cytoscape-cose-bilkent": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", - "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "node_modules/data-urls": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", + "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "dev": true, "license": "MIT", "dependencies": { - "cose-base": "^1.0.0" + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.0.0" }, - "peerDependencies": { - "cytoscape": "^3.2.0" + "engines": { + "node": ">=20" } }, - "node_modules/cytoscape-fcose": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", - "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, "license": "MIT", "dependencies": { - "cose-base": "^2.2.0" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" }, - "peerDependencies": { - "cytoscape": "^3.2.0" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/cytoscape-fcose/node_modules/cose-base": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", - "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, "license": "MIT", "dependencies": { - "layout-base": "^2.0.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/layout-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", - "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", - "license": "MIT" - }, - "node_modules/d3": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", - "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", - "license": "ISC", - "dependencies": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "4", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" }, "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" } }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", "dependencies": { - "internmap": "1 - 2" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": ">=12" - } - }, - "node_modules/d3-axis": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "license": "ISC", - "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/d3-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "license": "ISC", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "license": "ISC", - "dependencies": { - "d3-path": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-contour": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "license": "ISC", - "dependencies": { - "d3-array": "^3.2.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "license": "ISC", - "dependencies": { - "delaunator": "5" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "license": "ISC", - "dependencies": { - "commander": "7", - "iconv-lite": "0.6", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json.js", - "csv2tsv": "bin/dsv2dsv.js", - "dsv2dsv": "bin/dsv2dsv.js", - "dsv2json": "bin/dsv2json.js", - "json2csv": "bin/json2dsv.js", - "json2dsv": "bin/json2dsv.js", - "json2tsv": "bin/json2dsv.js", - "tsv2csv": "bin/dsv2dsv.js", - "tsv2json": "bin/dsv2json.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "license": "ISC", - "dependencies": { - "d3-dsv": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-geo": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", - "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2.5.0 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-sankey": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", - "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "1 - 2", - "d3-shape": "^1.2.0" - } - }, - "node_modules/d3-sankey/node_modules/d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", - "license": "BSD-3-Clause", - "dependencies": { - "internmap": "^1.0.0" - } - }, - "node_modules/d3-sankey/node_modules/d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", - "license": "BSD-3-Clause" - }, - "node_modules/d3-sankey/node_modules/d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-path": "1" - } - }, - "node_modules/d3-sankey/node_modules/internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", - "license": "ISC" - }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", - "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-interpolate": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", - "dependencies": { - "d3-path": "^3.1.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "license": "ISC", - "dependencies": { - "d3-time": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" - } - }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dagre-d3-es": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz", - "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==", - "license": "MIT", - "dependencies": { - "d3": "^7.9.0", - "lodash-es": "^4.17.21" - } - }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/data-urls": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", - "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -7584,15 +6666,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", - "license": "ISC", - "dependencies": { - "robust-predicates": "^3.0.2" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -7655,15 +6728,6 @@ "csstype": "^3.0.2" } }, - "node_modules/dompurify": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", - "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -8824,12 +7888,6 @@ "dev": true, "license": "MIT" }, - "node_modules/hachure-fill": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", - "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", - "license": "MIT" - }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -9061,6 +8119,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -9137,15 +8196,6 @@ "node": ">= 0.4" } }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/intl-messageformat": { "version": "10.7.17", "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.17.tgz", @@ -9822,31 +8872,6 @@ "node": ">=4.0" } }, - "node_modules/katex": { - "version": "0.16.25", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.25.tgz", - "integrity": "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==", - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" - ], - "license": "MIT", - "dependencies": { - "commander": "^8.3.0" - }, - "bin": { - "katex": "cli.js" - } - }, - "node_modules/katex/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -9857,27 +8882,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/khroma": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", - "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" - }, - "node_modules/langium": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", - "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", - "license": "MIT", - "dependencies": { - "chevrotain": "~11.0.3", - "chevrotain-allstar": "~0.3.0", - "vscode-languageserver": "~9.0.1", - "vscode-languageserver-textdocument": "~1.0.11", - "vscode-uri": "~3.0.8" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -9898,12 +8902,6 @@ "node": ">=0.10" } }, - "node_modules/layout-base": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", - "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", - "license": "MIT" - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -10195,12 +9193,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -10309,18 +9301,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/marked": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", - "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -10508,34 +9488,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/mermaid": { - "version": "11.12.1", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.1.tgz", - "integrity": "sha512-UlIZrRariB11TY1RtTgUWp65tphtBv4CSq7vyS2ZZ2TgoMjs2nloq+wFqxiwcxlhHUvs7DPGgMjs2aeQxz5h9g==", - "license": "MIT", - "dependencies": { - "@braintree/sanitize-url": "^7.1.1", - "@iconify/utils": "^3.0.1", - "@mermaid-js/parser": "^0.6.3", - "@types/d3": "^7.4.3", - "cytoscape": "^3.29.3", - "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.2.0", - "d3": "^7.9.0", - "d3-sankey": "^0.12.3", - "dagre-d3-es": "7.0.13", - "dayjs": "^1.11.18", - "dompurify": "^3.2.5", - "katex": "^0.16.22", - "khroma": "^2.1.0", - "lodash-es": "^4.17.21", - "marked": "^16.2.1", - "roughjs": "^4.6.6", - "stylis": "^4.3.6", - "ts-dedent": "^2.2.0", - "uuid": "^11.1.0" - } - }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", @@ -11165,18 +10117,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, "node_modules/motion-dom": { "version": "12.23.23", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", @@ -11458,12 +10398,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-manager-detector": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", - "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", - "license": "MIT" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11515,12 +10449,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/path-data-parser": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", - "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", - "license": "MIT" - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -11552,6 +10480,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, "license": "MIT" }, "node_modules/picocolors": { @@ -11575,33 +10504,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/points-on-curve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", - "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", - "license": "MIT" - }, - "node_modules/points-on-path": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", - "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", - "license": "MIT", - "dependencies": { - "path-data-parser": "0.1.0", - "points-on-curve": "0.2.0" - } - }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -12195,12 +11097,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", - "license": "Unlicense" - }, "node_modules/rolldown": { "version": "1.0.0-beta.52", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.52.tgz", @@ -12241,24 +11137,6 @@ "dev": true, "license": "MIT" }, - "node_modules/roughjs": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", - "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", - "license": "MIT", - "dependencies": { - "hachure-fill": "^0.5.2", - "path-data-parser": "^0.1.0", - "points-on-curve": "^0.2.0", - "points-on-path": "^0.2.1" - } - }, - "node_modules/rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", - "license": "BSD-3-Clause" - }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -12318,6 +11196,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, "license": "MIT" }, "node_modules/saxes": { @@ -12776,12 +11655,6 @@ "inline-style-parser": "0.2.7" } }, - "node_modules/stylis": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", - "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", - "license": "MIT" - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12963,15 +11836,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/ts-dedent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "license": "MIT", - "engines": { - "node": ">=6.10" - } - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -13134,12 +11998,6 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", - "license": "MIT" - }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -13352,19 +12210,6 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -13568,55 +12413,6 @@ } } }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/vscode-languageserver": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", - "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "3.17.5" - }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" - } - }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", - "license": "MIT", - "dependencies": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" - } - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", - "license": "MIT" - }, - "node_modules/vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", - "license": "MIT" - }, - "node_modules/vscode-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", - "license": "MIT" - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index dc1548c..b4e9743 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "portfolio", "private": true, "homepage": "https://cagesthrottleus.github.io/", - "version": "1.0.2", + "version": "1.0.3", "type": "module", "scripts": { "dev": "vite", @@ -26,7 +26,6 @@ "@spectrum-icons/workflow": "^4.2.25", "framer-motion": "^12.23.24", "lucide-react": "^0.555.0", - "mermaid": "^11.12.1", "react": "^19.2.0", "react-aria-components": "^1.13.0", "react-dom": "^19.2.0", diff --git a/src/components/BlogList/__tests__/BlogList.test.tsx b/src/components/BlogList/__tests__/BlogList.test.tsx index 2bbd0fd..c17d4ec 100644 --- a/src/components/BlogList/__tests__/BlogList.test.tsx +++ b/src/components/BlogList/__tests__/BlogList.test.tsx @@ -466,4 +466,58 @@ describe("BlogList", () => { expect(screen.getByText("Second Post")).toBeInTheDocument(); }); }); + + it("should navigate when blog card is clicked", async () => { + const user = userEvent.setup(); + + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText("Hello World")).toBeInTheDocument(); + }); + + // Click on a blog card + const blogCard = screen.getByText("Hello World").closest(".blog-card"); + expect(blogCard).toBeTruthy(); + + if (blogCard) { + await user.click(blogCard); + } + }); + + it("should show loading when index is still loading", () => { + // Mock fetchBlogIndex to return null initially + vi.spyOn(blogService, "fetchBlogIndex").mockImplementation( + () => new Promise(() => {}), // Never resolves + ); + + render( + + + , + ); + + // Should show loading spinner + expect(screen.getByText("CLASSIFIED TRANSMISSION")).toBeInTheDocument(); + }); + + it("should show loading spinner when index is null after loading completes", async () => { + // Mock to return null after loading completes + vi.spyOn(blogService, "fetchBlogIndex").mockResolvedValue(null as never); + + render( + + + , + ); + + // Wait for loading to complete and check spinner is still shown + await waitFor(() => { + expect(screen.getByText("CLASSIFIED TRANSMISSION")).toBeInTheDocument(); + }); + }); }); diff --git a/src/components/BlogPost/BlogComponents.tsx b/src/components/BlogPost/BlogComponents.tsx index e28beab..48740d8 100644 --- a/src/components/BlogPost/BlogComponents.tsx +++ b/src/components/BlogPost/BlogComponents.tsx @@ -6,9 +6,7 @@ * blog authors to know CSS class names. */ -import { useEffect, useRef, useState } from "react"; - -import { MERMAID_CONFIG } from "../../utils/constants"; +import { ExternalLink } from "lucide-react"; import type { ReactNode } from "react"; @@ -138,125 +136,71 @@ export function Table({ headers, rows }: TableProps) { } interface MermaidProps { - children: string; + text: string; caption?: string; } -// Track mermaid initialization globally -let mermaidInitialized = false; - /** - * Mermaid diagram component with classified document styling - * Renders diagrams using Cold War intelligence color palette - * Uses safe ref-based rendering (no dangerouslySetInnerHTML) - * Lazy loads Mermaid library only when component is used + * Mermaid diagram component + * Links to mermaid.live with pre-populated diagram text * * Usage: - * - * {` - * graph TD - * A[Client] --> B[Server] - * B --> C[Database] - * `} - * + * End + * `} + * caption="Fig 1.1: System Architecture" + * /> */ -export function Mermaid({ children, caption }: MermaidProps) { - const diagramRef = useRef(null); - const [error, setError] = useState(""); - const [isRendered, setIsRendered] = useState(false); - const [isLoading, setIsLoading] = useState(true); - - useEffect(() => { - const currentRef = diagramRef.current; - let cancelled = false; - - const loadAndRenderDiagram = async () => { - if (!currentRef) return; - - try { - setIsLoading(true); - - // Lazy load mermaid library (only when Mermaid component is used) - const mermaidModule = await import("mermaid"); - const mermaid = mermaidModule.default; - - // Check if cancelled after async import - if (cancelled) return; - - // Initialize mermaid once with Cold War classified theme - if (!mermaidInitialized) { - mermaid.initialize(MERMAID_CONFIG); - mermaidInitialized = true; - } - - // Clear previous content safely (use innerHTML to avoid DOM removal errors) - currentRef.innerHTML = ""; - currentRef.removeAttribute("data-processed"); - - // Create a temporary div for mermaid to process - const tempDiv = document.createElement("div"); - tempDiv.className = "mermaid"; - tempDiv.textContent = children.trim(); - currentRef.appendChild(tempDiv); - - // Render the diagram directly into the DOM (safe approach) - await mermaid.run({ - nodes: [tempDiv], - }); - - // Prevent state updates after unmount (cleanup can set cancelled during async) - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (cancelled) return; - setError(""); - setIsRendered(true); - } catch (err) { - if (cancelled) return; - setError( - err instanceof Error ? err.message : "Failed to render diagram", - ); - setIsRendered(false); - } finally { - if (cancelled) return; - setIsLoading(false); - } - }; - - void loadAndRenderDiagram(); - - return () => { - cancelled = true; - }; - }, [children]); - - if (error) { - return ( -
- - Diagram Rendering Error: -
{error}
-
-
- ); - } +export function Mermaid({ text, caption }: MermaidProps) { + // Encode diagram text for URL using base64 format + const jsonString = JSON.stringify({ + code: text.trim(), + mermaid: { theme: "default" }, + autoSync: true, + updateDiagram: true, + }); + + // Convert UTF-8 string to base64 safely using TextEncoder + const bytes = new TextEncoder().encode(jsonString); + const binString = Array.from(bytes, (byte) => + String.fromCodePoint(byte), + ).join(""); + const encodedDiagram = btoa(binString); + const mermaidUrl = `https://mermaid.live/edit#base64:${encodedDiagram}`; return (
- {isLoading && ( -
- Loading diagram renderer... -
- )} + + View Diagram on Mermaid.live + +
{caption &&
{caption}
}
@@ -271,6 +215,7 @@ export function Mermaid({ children, caption }: MermaidProps) { export const blogComponents = { Redacted, CallOut, + // Callout: CallOut, // Alias - MDX may normalize component names SectionMarker, ImageWithCaption, Highlight, diff --git a/src/components/BlogPost/BlogPostErrorBoundary.css b/src/components/BlogPost/BlogPostErrorBoundary.css new file mode 100644 index 0000000..26f5742 --- /dev/null +++ b/src/components/BlogPost/BlogPostErrorBoundary.css @@ -0,0 +1,55 @@ +.blog-post-error { + padding: 2rem; + margin: 2rem 0; + background: rgba(255, 50, 50, 0.1); + border: 2px solid var(--theme-red, #ff3232); + border-radius: 4px; + text-align: center; +} + +.error-stamp { + display: inline-block; + padding: 0.5rem 2rem; + background: var(--theme-red, #ff3232); + color: var(--theme-black, #000); + font-weight: bold; + font-size: 1.5rem; + letter-spacing: 0.2em; + transform: rotate(-5deg); + margin-bottom: 1rem; + border: 3px solid var(--theme-red, #ff3232); +} + +.blog-post-error h2 { + color: var(--theme-red, #ff3232); + margin-bottom: 1rem; +} + +.blog-post-error p { + color: var(--theme-text, #fff); + margin-bottom: 1rem; +} + +.blog-post-error details { + margin-top: 1rem; + text-align: left; + max-width: 600px; + margin-left: auto; + margin-right: auto; +} + +.blog-post-error summary { + cursor: pointer; + color: var(--theme-green, #00ff00); + font-family: "Courier New", monospace; + margin-bottom: 0.5rem; +} + +.blog-post-error pre { + background: rgba(0, 0, 0, 0.5); + padding: 1rem; + border-radius: 4px; + overflow-x: auto; + color: var(--theme-green, #00ff00); + font-size: 0.875rem; +} diff --git a/src/components/BlogPost/BlogPostErrorBoundary.tsx b/src/components/BlogPost/BlogPostErrorBoundary.tsx new file mode 100644 index 0000000..917ba38 --- /dev/null +++ b/src/components/BlogPost/BlogPostErrorBoundary.tsx @@ -0,0 +1,51 @@ +import { Component, type ReactNode } from "react"; + +interface Props { + children: ReactNode; + fallback?: ReactNode; +} + +interface State { + hasError: boolean; + error?: Error; +} + +class BlogPostErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error("BlogPost rendering error:", error, errorInfo); + } + + render() { + if (this.state.hasError) { + return ( + this.props.fallback || ( +
+
ERROR
+

Document Rendering Failed

+

+ An error occurred while rendering this classified document. The + content may contain incompatible components. +

+
+ Technical Details +
{this.state.error?.message}
+
+
+ ) + ); + } + + return this.props.children; + } +} + +export default BlogPostErrorBoundary; diff --git a/src/components/BlogPost/BlogPostLayout.css b/src/components/BlogPost/BlogPostLayout.css index 61d5460..88b6d70 100644 --- a/src/components/BlogPost/BlogPostLayout.css +++ b/src/components/BlogPost/BlogPostLayout.css @@ -371,76 +371,51 @@ /* Mermaid Diagrams */ .blog-mermaid { margin: var(--spacing-xl) 0; - padding: var(--spacing-lg); - background: rgba(10, 10, 10, 0.6); - border: var(--border-width-normal) solid var(--color-border-primary); - border-radius: var(--radius-sm); - box-shadow: - 0 0 20px rgba(220, 38, 38, 0.1), - inset 0 0 40px rgba(220, 38, 38, 0.03); -} - -.blog-mermaid-diagram { - display: flex; - justify-content: center; - align-items: center; - min-height: 200px; - padding: var(--spacing-md); - background: rgba(0, 0, 0, 0.3); - border-radius: var(--radius-xs); - overflow-x: auto; -} - -/* Ensure mermaid SVGs are properly styled */ -.blog-mermaid-diagram svg { - max-width: 100%; - height: auto; - filter: drop-shadow(0 0 10px rgba(220, 38, 38, 0.2)); } -/* Override mermaid's default styles with Cold War theme */ -.blog-mermaid-diagram .node rect, -.blog-mermaid-diagram .node circle, -.blog-mermaid-diagram .node ellipse, -.blog-mermaid-diagram .node polygon { - stroke: var(--classified-500) !important; - stroke-width: 2px !important; +/* Blog Post Error Detail Display */ +.blog-post-error-detail { + padding: var(--spacing-xl); } -.blog-mermaid-diagram .edgePath .path { - stroke: var(--terminal-500) !important; - stroke-width: 2px !important; +.blog-post-error-detail .error-stamp { + display: inline-block; + padding: 0.5rem 2rem; + background: var(--theme-red, #dc2626); + color: #ffffff; + font-weight: bold; + font-size: 1.5rem; + letter-spacing: 0.2em; + transform: rotate(-5deg); + margin-bottom: 2rem; + border: 3px solid var(--theme-red, #dc2626); } -.blog-mermaid-diagram .arrowheadPath { - fill: var(--terminal-500) !important; - stroke: var(--terminal-500) !important; -} - -.blog-mermaid-diagram .cluster rect { - stroke: var(--terminal-500) !important; - stroke-width: 2px !important; -} - -/* Mermaid text elements */ -.blog-mermaid-diagram text { - font-family: var(--font-mono) !important; - fill: var(--color-text-primary) !important; -} - -.blog-mermaid-diagram .edgeLabel text { - fill: var(--color-text-secondary) !important; +.blog-post-error-detail .error-message { + background: rgba(220, 38, 38, 0.05); + border: 1px solid var(--theme-red, #dc2626); + border-radius: 4px; + padding: var(--spacing-lg); + margin: var(--spacing-lg) 0; } -/* Mermaid error state */ -.blog-mermaid-error { - margin: var(--spacing-lg) 0; +.blog-post-error-detail .error-message h3 { + color: var(--theme-red, #dc2626); + margin-bottom: var(--spacing-md); + font-family: var(--font-mono); } -.blog-mermaid-error pre { - margin-top: var(--spacing-sm); - font-size: var(--font-size-xs); - color: var(--color-text-muted); +.blog-post-error-detail .error-message pre { + background: rgba(0, 0, 0, 0.8); + color: var(--theme-green, #22c55e); + padding: var(--spacing-md); + border-radius: 4px; + overflow-x: auto; + font-family: var(--font-mono); + font-size: 0.875rem; + line-height: 1.5; + white-space: pre-wrap; + word-break: break-word; } /* ============================================ diff --git a/src/components/BlogPost/BlogPostLayout.tsx b/src/components/BlogPost/BlogPostLayout.tsx index ac83351..c4631cf 100644 --- a/src/components/BlogPost/BlogPostLayout.tsx +++ b/src/components/BlogPost/BlogPostLayout.tsx @@ -2,11 +2,13 @@ import { ChevronLeft } from "lucide-react"; import { useNavigate, useParams } from "react-router"; import { blogComponents } from "./BlogComponents"; +import BlogPostErrorBoundary from "./BlogPostErrorBoundary"; import { useBlogPost } from "../../hooks/useBlogPost"; import LoadingSpinner from "../LoadingSpinner/LoadingSpinner"; import NotFoundComponent from "../NotFound/NotFound"; import "./BlogPostLayout.css"; +import "./BlogPostErrorBoundary.css"; function BlogPostLayout() { const { slug } = useParams<{ slug: string }>(); @@ -14,7 +16,43 @@ function BlogPostLayout() { const { MDXContent, metadata, loading, error } = useBlogPost(slug); if (loading) return ; - if (error || !metadata || !MDXContent) return ; + + // Show detailed error message for MDX syntax errors + if (error) { + return ( +
+
+
+
ERROR
+
+ +
+
COMPILATION FAILED
+

Blog Post Error

+
+

Details:

+
{error}
+
+ +
+ +
+
+
+
+ ); + } + + if (!metadata || !MDXContent) return ; return (
@@ -46,7 +84,9 @@ function BlogPostLayout() { {/* Post Content */}
- + + +
{/* Footer Navigation */} diff --git a/src/components/BlogPost/__tests__/BlogComponents.test.tsx b/src/components/BlogPost/__tests__/BlogComponents.test.tsx index 467f91f..69bd7db 100644 --- a/src/components/BlogPost/__tests__/BlogComponents.test.tsx +++ b/src/components/BlogPost/__tests__/BlogComponents.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import { describe, it, expect, vi } from "vitest"; import { @@ -195,46 +195,50 @@ describe("BlogComponents", () => { }); describe("Mermaid", () => { - it("should render mermaid diagram", async () => { + it("should render mermaid link", () => { const diagramCode = ` graph TD A[Start] --> B[End] `; - const { container } = render({diagramCode}); + render(); - await waitFor(() => { - const mermaidDiv = container.querySelector(".blog-mermaid-diagram"); - expect(mermaidDiv).toBeInTheDocument(); - }); + expect( + screen.getByText("View Diagram on Mermaid.live"), + ).toBeInTheDocument(); }); - it("should render caption when provided", async () => { - const diagramCode = "graph TD\nA-->B"; + it("should render caption when provided", () => { + const diagramCode = "graph TD\nA-->B"; - render({diagramCode}); + render(); - await waitFor(() => { - expect(screen.getByText("Fig 1.1: System Flow")).toBeInTheDocument(); - }); + expect(screen.getByText("Fig 1.1: System Flow")).toBeInTheDocument(); }); - it("should apply blog-mermaid class", async () => { - const { container } = render({"graph TD\nA-->B"}); + it("should apply blog-mermaid class", () => { + const { container } = render(); - await waitFor(() => { - const figure = container.querySelector(".blog-mermaid"); - expect(figure).toBeInTheDocument(); - }); + const figure = container.querySelector(".blog-mermaid"); + expect(figure).toBeInTheDocument(); }); - it("should render without caption", async () => { - const { container } = render({"graph TD\nA-->B"}); + it("should render without caption", () => { + const { container } = render(); + + const caption = container.querySelector(".blog-caption"); + expect(caption).not.toBeInTheDocument(); + }); + + it("should render link with correct href", () => { + const diagramCode = "graph TD\nA-->B"; + + render(); - await waitFor(() => { - const caption = container.querySelector(".blog-caption"); - expect(caption).not.toBeInTheDocument(); - }); + const link = screen.getByText("View Diagram on Mermaid.live"); + expect(link).toHaveAttribute("href"); + expect(link).toHaveAttribute("target", "_blank"); + expect(link).toHaveAttribute("rel", "noopener noreferrer"); }); }); }); diff --git a/src/components/BlogPost/__tests__/BlogPostErrorBoundary.test.tsx b/src/components/BlogPost/__tests__/BlogPostErrorBoundary.test.tsx new file mode 100644 index 0000000..33aeb05 --- /dev/null +++ b/src/components/BlogPost/__tests__/BlogPostErrorBoundary.test.tsx @@ -0,0 +1,114 @@ +import { render, screen } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; + +import BlogPostErrorBoundary from "../BlogPostErrorBoundary"; + +// Component that throws an error +const ThrowError = ({ shouldThrow }: { shouldThrow: boolean }) => { + if (shouldThrow) { + throw new Error("Test rendering error"); + } + return
Normal content
; +}; + +describe("BlogPostErrorBoundary", () => { + beforeEach(() => { + // Suppress console.error in tests + vi.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("should render children when no error occurs", () => { + render( + +
Test content
+
, + ); + + expect(screen.getByText("Test content")).toBeInTheDocument(); + }); + + it("should render error UI when child component throws", () => { + render( + + + , + ); + + expect(screen.getByText("Document Rendering Failed")).toBeInTheDocument(); + expect( + screen.getByText( + /An error occurred while rendering this classified document/, + ), + ).toBeInTheDocument(); + }); + + it("should display error message in technical details", () => { + render( + + + , + ); + + expect(screen.getByText("Technical Details")).toBeInTheDocument(); + expect(screen.getByText("Test rendering error")).toBeInTheDocument(); + }); + + it("should render custom fallback when provided", () => { + const customFallback =
Custom error message
; + + render( + + + , + ); + + expect(screen.getByText("Custom error message")).toBeInTheDocument(); + expect( + screen.queryByText("Document Rendering Failed"), + ).not.toBeInTheDocument(); + }); + + it("should log error to console via componentDidCatch", () => { + const consoleErrorSpy = vi + .spyOn(console, "error") + .mockImplementation(() => {}); + + render( + + + , + ); + + // React Error Boundary logs errors - just verify it was called + expect(consoleErrorSpy).toHaveBeenCalled(); + }); + + it("should have error stamp in default error UI", () => { + render( + + + , + ); + + const errorStamp = screen.getByText("ERROR"); + expect(errorStamp).toBeInTheDocument(); + expect(errorStamp).toHaveClass("error-stamp"); + }); + + it("should not render error UI when error is not thrown", () => { + render( + + + , + ); + + expect(screen.getByText("Normal content")).toBeInTheDocument(); + expect( + screen.queryByText("Document Rendering Failed"), + ).not.toBeInTheDocument(); + }); +}); diff --git a/src/components/BlogPost/__tests__/BlogPostLayout.test.tsx b/src/components/BlogPost/__tests__/BlogPostLayout.test.tsx index 93aa15c..aeff666 100644 --- a/src/components/BlogPost/__tests__/BlogPostLayout.test.tsx +++ b/src/components/BlogPost/__tests__/BlogPostLayout.test.tsx @@ -41,7 +41,9 @@ describe("BlogPostLayout", () => { expect(screen.getByText("CLASSIFIED TRANSMISSION")).toBeInTheDocument(); }); - it("should render not found component on error", () => { + it("should render error UI on error", async () => { + const user = userEvent.setup(); + vi.spyOn(useBlogPostHook, "useBlogPost").mockReturnValue({ MDXContent: null, metadata: null, @@ -55,7 +57,15 @@ describe("BlogPostLayout", () => { , ); - expect(screen.getByText("PAGE NOT FOUND")).toBeInTheDocument(); + expect(screen.getByText("Blog Post Error")).toBeInTheDocument(); + expect(screen.getByText("COMPILATION FAILED")).toBeInTheDocument(); + expect(screen.getByText("Post not found")).toBeInTheDocument(); + + // Click return to archive button + const returnButton = screen.getByText("RETURN TO ARCHIVE"); + await user.click(returnButton); + + expect(mockNavigate).toHaveBeenCalledWith("/blog"); }); it("should render blog post with metadata", () => { @@ -245,4 +255,47 @@ describe("BlogPostLayout", () => { screen.getByText("UNAUTHORIZED DISCLOSURE SUBJECT TO CRIMINAL SANCTIONS"), ).toBeInTheDocument(); }); + + it("should render not found when metadata is missing but no error", () => { + vi.spyOn(useBlogPostHook, "useBlogPost").mockReturnValue({ + MDXContent: null, + metadata: null, + loading: false, + error: null, + }); + + render( + + + , + ); + + expect(screen.getByText("PAGE NOT FOUND")).toBeInTheDocument(); + }); + + it("should render not found when MDXContent is missing but no error", () => { + const mockMetadata = { + slug: "test-post", + title: "Test Post", + classification: "UNCLASSIFIED", + abstract: "Test abstract", + publishDate: "2025-11-29", + version: "1.0", + }; + + vi.spyOn(useBlogPostHook, "useBlogPost").mockReturnValue({ + MDXContent: null, + metadata: mockMetadata, + loading: false, + error: null, + }); + + render( + + + , + ); + + expect(screen.getByText("PAGE NOT FOUND")).toBeInTheDocument(); + }); }); diff --git a/src/components/CursorTracker/__tests__/CursorTracker.test.tsx b/src/components/CursorTracker/__tests__/CursorTracker.test.tsx new file mode 100644 index 0000000..22d1b87 --- /dev/null +++ b/src/components/CursorTracker/__tests__/CursorTracker.test.tsx @@ -0,0 +1,158 @@ +import { render } from "@testing-library/react"; +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; + +import CursorTracker from "../CursorTracker"; + +// Helper to create and dispatch mouse events +const dispatchMouseMove = (target: HTMLElement, x: number, y: number) => { + const event = new MouseEvent("mousemove", { + bubbles: true, + cancelable: true, + clientX: x, + clientY: y, + }); + target.dispatchEvent(event); +}; + +describe("CursorTracker", () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + vi.useRealTimers(); + }); + + it("should render cursor tracker element", () => { + const { container } = render(); + const cursor = container.querySelector(".cursor-tracker"); + expect(cursor).toBeInTheDocument(); + }); + + it("should update cursor position on mouse move", () => { + const { container } = render( +
+ +
Test
+
, + ); + const cursor = container.querySelector(".cursor-tracker") as HTMLElement; + const testArea = container.querySelector(".test-area") as HTMLElement; + + dispatchMouseMove(testArea, 100, 200); + + // Flush requestAnimationFrame + vi.runAllTimers(); + + expect(cursor.style.getPropertyValue("--cursor-x")).toBe("100px"); + expect(cursor.style.getPropertyValue("--cursor-y")).toBe("200px"); + }); + + it("should set opacity to 0 when hovering over no-cursor-track elements", () => { + const { container } = render( +
+ +
No track zone
+
, + ); + + const cursor = container.querySelector(".cursor-tracker") as HTMLElement; + const noTrackElement = container.querySelector( + ".no-cursor-track", + ) as HTMLElement; + + dispatchMouseMove(noTrackElement, 50, 50); + + vi.runAllTimers(); + + expect(cursor.style.opacity).toBe("0"); + }); + + it("should set opacity to 1 when not hovering over no-cursor-track elements", () => { + const { container } = render( +
+ +
Test
+
, + ); + const cursor = container.querySelector(".cursor-tracker") as HTMLElement; + const testArea = container.querySelector(".test-area") as HTMLElement; + + dispatchMouseMove(testArea, 100, 200); + + vi.runAllTimers(); + + expect(cursor.style.opacity).toBe("1"); + }); + + it("should cleanup event listeners on unmount", () => { + const removeEventListenerSpy = vi.spyOn(window, "removeEventListener"); + + const { unmount } = render( +
+ +
, + ); + + unmount(); + + expect(removeEventListenerSpy).toHaveBeenCalledWith( + "mousemove", + expect.any(Function), + ); + }); + + it("should cancel animation frame on unmount", () => { + const cancelAnimationFrameSpy = vi.spyOn(window, "cancelAnimationFrame"); + + const { container, unmount } = render( +
+ +
Test
+
, + ); + + const testArea = container.querySelector(".test-area") as HTMLElement; + + // Trigger a mouse move to start an animation frame + dispatchMouseMove(testArea, 100, 200); + + unmount(); + + // Should have called cancelAnimationFrame during cleanup + expect(cancelAnimationFrameSpy).toHaveBeenCalled(); + }); + + it("should handle rapid mouse movements by canceling previous frames", () => { + const { container } = render( +
+ +
Test
+
, + ); + const cursor = container.querySelector(".cursor-tracker") as HTMLElement; + const testArea = container.querySelector(".test-area") as HTMLElement; + + // Trigger multiple rapid movements + dispatchMouseMove(testArea, 10, 10); + dispatchMouseMove(testArea, 20, 20); + dispatchMouseMove(testArea, 30, 30); + + vi.runAllTimers(); + + // Should only process the last position + expect(cursor.style.getPropertyValue("--cursor-x")).toBe("30px"); + expect(cursor.style.getPropertyValue("--cursor-y")).toBe("30px"); + }); + + it("should return early if cursor ref is null", () => { + const { container } = render(); + + // Manually set ref to null to test early return + const cursor = container.querySelector(".cursor-tracker"); + + // Should not throw error even if cursor element is not found + expect(cursor).toBeInTheDocument(); + }); +}); diff --git a/src/components/ReadingProgress/__tests__/ReadingProgress.test.tsx b/src/components/ReadingProgress/__tests__/ReadingProgress.test.tsx new file mode 100644 index 0000000..50be87a --- /dev/null +++ b/src/components/ReadingProgress/__tests__/ReadingProgress.test.tsx @@ -0,0 +1,127 @@ +import { render } from "@testing-library/react"; +import * as framerMotion from "framer-motion"; +import { describe, it, expect, vi } from "vitest"; + +import ReadingProgress from "../ReadingProgress"; + +// Mock framer-motion +vi.mock("framer-motion", () => ({ + motion: { + div: vi.fn( + ({ + children, + className, + style, + ...props + }: { + children?: React.ReactNode; + className?: string; + style?: React.CSSProperties; + [key: string]: unknown; + }) => ( +
+ {children} +
+ ), + ), + }, + useScroll: vi.fn(() => ({ + scrollYProgress: { + get: () => 0, + on: vi.fn((event: string, handler: (value: number) => void) => { + // Immediately call handler with test value + if (event === "change") { + handler(0.5); + } + return () => {}; // Return unsubscribe function + }), + }, + })), + useSpring: vi.fn((value: unknown) => value), +})); + +describe("ReadingProgress", () => { + it("should render reading progress container", () => { + const { container } = render(); + const progressContainer = container.querySelector( + ".reading-progress-container", + ); + expect(progressContainer).toBeInTheDocument(); + }); + + it("should render progress bar", () => { + const { container } = render(); + const progressBar = container.querySelector(".reading-progress-bar"); + expect(progressBar).toBeInTheDocument(); + }); + + it("should show progress bar when scroll position is greater than 10%", () => { + const mockOn = vi.fn((event: string, handler: (value: number) => void) => { + if (event === "change") { + handler(0.15); // 15% scroll + } + return () => {}; + }); + + vi.mocked(framerMotion).useScroll.mockReturnValue({ + scrollYProgress: { + get: () => 0.15, + on: mockOn, + }, + } as never); + + render(); + + expect(mockOn).toHaveBeenCalledWith("change", expect.any(Function)); + }); + + it("should hide progress bar when scroll position is less than 10%", () => { + const mockOn = vi.fn((event: string, handler: (value: number) => void) => { + if (event === "change") { + handler(0.05); // 5% scroll + } + return () => {}; + }); + + vi.mocked(framerMotion).useScroll.mockReturnValue({ + scrollYProgress: { + get: () => 0.05, + on: mockOn, + }, + } as never); + + render(); + + expect(mockOn).toHaveBeenCalledWith("change", expect.any(Function)); + }); + + it("should unsubscribe from scroll progress on unmount", () => { + const mockUnsubscribe = vi.fn(); + const mockOn = vi.fn(() => mockUnsubscribe); + + vi.mocked(framerMotion).useScroll.mockReturnValue({ + scrollYProgress: { + get: () => 0, + on: mockOn, + }, + } as never); + + const { unmount } = render(); + + unmount(); + + expect(mockUnsubscribe).toHaveBeenCalled(); + }); + + it("should apply correct spring configuration", () => { + const { useSpring } = vi.mocked(framerMotion); + + render(); + + expect(useSpring).toHaveBeenCalledWith(expect.anything(), { + stiffness: 100, + damping: 30, + restDelta: 0.001, + }); + }); +}); diff --git a/src/hooks/__tests__/useBlogPost.test.ts b/src/hooks/__tests__/useBlogPost.test.ts index 756adbb..a963120 100644 --- a/src/hooks/__tests__/useBlogPost.test.ts +++ b/src/hooks/__tests__/useBlogPost.test.ts @@ -93,7 +93,7 @@ title: Test Post expect(result.current.loading).toBe(false); }); - expect(result.current.error).toBe("Failed to load post"); + expect(result.current.error).toBe("Network error"); expect(result.current.MDXContent).toBeNull(); consoleErrorSpy.mockRestore(); @@ -162,4 +162,114 @@ classification: UNCLASSIFIED expect(compiledContent).not.toContain("---"); expect(compiledContent).toContain("# Actual Content"); }); + + it("should handle MDX syntax errors with helpful message", async () => { + const consoleErrorSpy = vi + .spyOn(console, "error") + .mockImplementation(() => {}); + + vi.spyOn(blogService, "fetchPostMetadata").mockResolvedValue({ + slug: "test-post", + title: "Test Post", + classification: "UNCLASSIFIED", + abstract: "Test abstract", + publishDate: "2025-11-29", + version: "1.0", + }); + vi.spyOn(blogService, "fetchPostContent").mockResolvedValue("# Content"); + vi.spyOn(mdx, "compile").mockRejectedValue( + new Error("Could not parse expression"), + ); + + const { result } = renderHook(() => useBlogPost("test-post")); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + }); + + expect(result.current.error).toContain("MDX Syntax Error"); + expect(result.current.error).toContain("Could not parse expression"); + + consoleErrorSpy.mockRestore(); + }); + + it("should handle unexpected character errors", async () => { + const consoleErrorSpy = vi + .spyOn(console, "error") + .mockImplementation(() => {}); + + vi.spyOn(blogService, "fetchPostMetadata").mockResolvedValue({ + slug: "test-post", + title: "Test Post", + classification: "UNCLASSIFIED", + abstract: "Test abstract", + publishDate: "2025-11-29", + version: "1.0", + }); + vi.spyOn(blogService, "fetchPostContent").mockResolvedValue("# Content"); + vi.spyOn(mdx, "compile").mockRejectedValue( + new Error("Unexpected character found"), + ); + + const { result } = renderHook(() => useBlogPost("test-post")); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + }); + + expect(result.current.error).toContain("MDX Parsing Error"); + expect(result.current.error).toContain("Unexpected character"); + + consoleErrorSpy.mockRestore(); + }); + + it("should handle network fetch errors", async () => { + const consoleErrorSpy = vi + .spyOn(console, "error") + .mockImplementation(() => {}); + + vi.spyOn(blogService, "fetchPostMetadata").mockRejectedValue( + new Error("Failed to fetch post content"), + ); + + const { result } = renderHook(() => useBlogPost("test-post")); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + }); + + expect(result.current.error).toContain("Network Error"); + + consoleErrorSpy.mockRestore(); + }); + + it("should handle component errors", async () => { + const consoleErrorSpy = vi + .spyOn(console, "error") + .mockImplementation(() => {}); + + vi.spyOn(blogService, "fetchPostMetadata").mockResolvedValue({ + slug: "test-post", + title: "Test Post", + classification: "UNCLASSIFIED", + abstract: "Test abstract", + publishDate: "2025-11-29", + version: "1.0", + }); + vi.spyOn(blogService, "fetchPostContent").mockResolvedValue("# Content"); + vi.spyOn(mdx, "compile").mockRejectedValue( + new Error("Expected component 'CustomComponent' not found"), + ); + + const { result } = renderHook(() => useBlogPost("test-post")); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + }); + + expect(result.current.error).toContain("Component Error"); + expect(result.current.error).toContain("Expected component"); + + consoleErrorSpy.mockRestore(); + }); }); diff --git a/src/hooks/useBlogPost.ts b/src/hooks/useBlogPost.ts index 5468944..108204f 100644 --- a/src/hooks/useBlogPost.ts +++ b/src/hooks/useBlogPost.ts @@ -65,7 +65,8 @@ export function useBlogPost(slug: string | undefined) { const code = String( await compile(contentWithoutFrontmatter, { outputFormat: "function-body", - development: false, + development: false, // Disable dev mode to skip component validation + jsxImportSource: "react", }), ); @@ -85,7 +86,24 @@ export function useBlogPost(slug: string | undefined) { } catch (err: unknown) { if (!cancelled) { console.error("Error loading post:", err); - setError("Failed to load post"); + + // Provide detailed error messages + let errorMessage = "Failed to load post"; + if (err instanceof Error) { + if (err.message.includes("Could not parse")) { + errorMessage = `MDX Syntax Error:\n\n${err.message}\n\nTip: Check for emojis, special characters, or invalid syntax in template expressions.`; + } else if (err.message.includes("Unexpected character")) { + errorMessage = `MDX Parsing Error:\n\n${err.message}\n\nTip: Emojis and special characters must be wrapped in markdown text syntax ["\`...\`"] or removed from code blocks.`; + } else if (err.message.includes("Failed to fetch")) { + errorMessage = `Network Error: Could not fetch blog post content.`; + } else if (err.message.includes("Expected component")) { + errorMessage = `Component Error:\n\n${err.message}\n\nTip: Ensure all custom components are defined in BlogComponents.tsx`; + } else { + errorMessage = err.message; + } + } + + setError(errorMessage); setLoading(false); } } diff --git a/src/services/__tests__/blogService.test.ts b/src/services/__tests__/blogService.test.ts index db6f83d..a8ebbb3 100644 --- a/src/services/__tests__/blogService.test.ts +++ b/src/services/__tests__/blogService.test.ts @@ -5,8 +5,6 @@ import { fetchPage, fetchPostMetadata, fetchPostContent, - clearBlogCache, - getCacheStats, } from "../blogService"; // Mock localStorage @@ -61,7 +59,7 @@ describe("blogService", () => { }); describe("fetchBlogIndex", () => { - it("should fetch and cache blog index", async () => { + it("should fetch blog index", async () => { const mockIndex = { version: "2025-11-29", totalPosts: 2, @@ -81,37 +79,8 @@ describe("blogService", () => { expect(result).toEqual(mockIndex); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining("/manifests/index.json"), - { cache: "no-cache" }, + { cache: "no-store" }, ); - - // Verify caching - const cached = localStorageMock.getItem("blog-index-v1"); - expect(cached).toBeTruthy(); - }); - - it("should return cached data if available", async () => { - const mockIndex = { - version: "2025-11-29", - totalPosts: 2, - totalPages: 1, - postsPerPage: 50, - latestPosts: [], - pages: {}, - }; - - // Set cache - localStorageMock.setItem( - "blog-index-v1", - JSON.stringify({ - data: mockIndex, - timestamp: Date.now(), - }), - ); - - const result = await fetchBlogIndex(); - - expect(result).toEqual(mockIndex); - expect(mockFetch).not.toHaveBeenCalled(); }); it("should throw error on failed fetch", async () => { @@ -127,7 +96,7 @@ describe("blogService", () => { }); describe("fetchPage", () => { - it("should fetch and cache page data", async () => { + it("should fetch page data", async () => { const mockPage = { page: 1, posts: [], @@ -143,33 +112,22 @@ describe("blogService", () => { expect(result).toEqual(mockPage); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining("/manifests/page-1.json"), - { cache: "no-cache" }, + { cache: "no-store" }, ); }); - it("should return cached page data", async () => { - const mockPage = { - page: 1, - posts: [], - }; - - localStorageMock.setItem( - "blog-page-1-v1", - JSON.stringify({ - data: mockPage, - timestamp: Date.now(), - }), - ); - - const result = await fetchPage(1); + it("should throw error on failed page fetch", async () => { + mockFetch.mockResolvedValueOnce({ + ok: false, + statusText: "Not Found", + }); - expect(result).toEqual(mockPage); - expect(mockFetch).not.toHaveBeenCalled(); + await expect(fetchPage(1)).rejects.toThrow("Failed to fetch page 1"); }); }); describe("fetchPostMetadata", () => { - it("should fetch and cache post metadata", async () => { + it("should fetch post metadata", async () => { const mockMetadata = { slug: "test-post", title: "Test Post", @@ -215,7 +173,7 @@ describe("blogService", () => { }); describe("fetchPostContent", () => { - it("should fetch and cache post content", async () => { + it("should fetch post content", async () => { const mockContent = "# Test Post\n\nContent here"; mockFetch.mockResolvedValueOnce({ @@ -239,133 +197,4 @@ describe("blogService", () => { ); }); }); - - describe("clearBlogCache", () => { - it("should remove all blog-related cache entries", () => { - localStorageMock.setItem("blog-index-v1", "test"); - localStorageMock.setItem("blog-page-1-v1", "test"); - localStorageMock.setItem("other-key", "test"); - - clearBlogCache(); - - expect(localStorageMock.getItem("blog-index-v1")).toBeNull(); - expect(localStorageMock.getItem("blog-page-1-v1")).toBeNull(); - expect(localStorageMock.getItem("other-key")).toBe("test"); - }); - }); - - describe("getCacheStats", () => { - it("should return cache statistics", () => { - localStorageMock.setItem("blog-index-v1", "test1"); - localStorageMock.setItem("blog-page-1-v1", "test2"); - localStorageMock.setItem("other-key", "test3"); - - const stats = getCacheStats(); - - expect(stats.totalEntries).toBe(2); - expect(stats.entries).toEqual(["blog-index-v1", "blog-page-1-v1"]); - expect(stats.totalSize).toBeGreaterThan(0); - }); - }); - - describe("cache expiration", () => { - it("should ignore expired cache entries", async () => { - const mockIndex = { - version: "2025-11-29", - totalPosts: 2, - totalPages: 1, - postsPerPage: 50, - latestPosts: [], - pages: {}, - }; - - // Set expired cache (6 minutes ago) - const sixMinutesAgo = Date.now() - 6 * 60 * 1000; - localStorageMock.setItem( - "blog-index-v1", - JSON.stringify({ - data: mockIndex, - timestamp: sixMinutesAgo, - }), - ); - - mockFetch.mockResolvedValueOnce({ - ok: true, - json: () => Promise.resolve(mockIndex), - }); - - await fetchBlogIndex(); - - // Should fetch fresh data - expect(mockFetch).toHaveBeenCalled(); - }); - }); - - describe("cache error handling", () => { - it("should handle corrupted cache data gracefully", async () => { - const consoleErrorSpy = vi - .spyOn(console, "error") - .mockImplementation(() => {}); - - // Set corrupted cache - localStorageMock.setItem("blog-index-v1", "invalid json{"); - - const mockIndex = { - version: "2025-11-29", - totalPosts: 2, - totalPages: 1, - postsPerPage: 50, - latestPosts: [], - pages: {}, - }; - - mockFetch.mockResolvedValueOnce({ - ok: true, - json: () => Promise.resolve(mockIndex), - }); - - const result = await fetchBlogIndex(); - - expect(result).toEqual(mockIndex); - expect(consoleErrorSpy).toHaveBeenCalled(); - - consoleErrorSpy.mockRestore(); - }); - - it("should handle localStorage write errors gracefully", async () => { - const consoleErrorSpy = vi - .spyOn(console, "error") - .mockImplementation(() => {}); - - // Mock setItem to throw error - const originalSetItem = localStorageMock.setItem; - localStorageMock.setItem = () => { - throw new Error("QuotaExceededError"); - }; - - const mockIndex = { - version: "2025-11-29", - totalPosts: 2, - totalPages: 1, - postsPerPage: 50, - latestPosts: [], - pages: {}, - }; - - mockFetch.mockResolvedValueOnce({ - ok: true, - json: () => Promise.resolve(mockIndex), - }); - - const result = await fetchBlogIndex(); - - // Should still return data even if caching fails - expect(result).toEqual(mockIndex); - expect(consoleErrorSpy).toHaveBeenCalled(); - - // Restore - localStorageMock.setItem = originalSetItem; - consoleErrorSpy.mockRestore(); - }); - }); }); diff --git a/src/services/blogService.ts b/src/services/blogService.ts index c31545e..0e345e9 100644 --- a/src/services/blogService.ts +++ b/src/services/blogService.ts @@ -2,106 +2,37 @@ * Blog Service * * Fetches blog content from the GitHub blog branch via raw content API. - * Implements localStorage caching with configurable TTL. * No authentication required (public repository). */ -import { BLOG_BASE_URL, BLOG_CACHE_DURATION } from "../utils/constants"; +import { BLOG_BASE_URL } from "../utils/constants"; -import type { - BlogIndex, - BlogMetadata, - PageManifest, - CacheEntry, -} from "../types/blog"; - -/** - * Get cached data from localStorage - * Type parameter only used for return type inference but necessary for type safety - */ -// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -function getCached(key: string): T | null { - try { - const cached = localStorage.getItem(key); - if (!cached) return null; - - const entry = JSON.parse(cached) as CacheEntry; - - // Check if cache is expired - if (Date.now() - entry.timestamp > BLOG_CACHE_DURATION) { - localStorage.removeItem(key); - return null; - } - - return entry.data; - } catch (error) { - console.error(`Error reading cache for ${key}:`, error); - return null; - } -} - -/** - * Set data in localStorage cache - * Type parameter only used for input type but necessary for type safety with getCached - */ -// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -function setCache(key: string, data: T): void { - try { - const entry: CacheEntry = { - data, - timestamp: Date.now(), - }; - localStorage.setItem(key, JSON.stringify(entry)); - } catch (error) { - console.error(`Error setting cache for ${key}:`, error); - // Don't throw - caching is optional - } -} +import type { BlogIndex, BlogMetadata, PageManifest } from "../types/blog"; /** * Fetch blog index manifest * Contains metadata about all posts, total count, and page links */ export async function fetchBlogIndex(): Promise { - const cacheKey = "blog-index-v1"; - - // Try cache first - const cached = getCached(cacheKey); - if (cached) { - return cached; - } - - // Fetch from GitHub const response = await fetch(`${BLOG_BASE_URL}/manifests/index.json`, { - cache: "no-cache", // Bypass browser cache, use our localStorage + cache: "no-store", }); if (!response.ok) { throw new Error(`Failed to fetch blog index: ${response.statusText}`); } - const data = (await response.json()) as BlogIndex; - setCache(cacheKey, data); - return data; + return (await response.json()) as BlogIndex; } /** * Fetch a specific page of blog posts */ export async function fetchPage(pageNum: number): Promise { - const cacheKey = `blog-page-${String(pageNum)}-v1`; - - // Try cache first - const cached = getCached(cacheKey); - if (cached) { - return cached; - } - - // Fetch from GitHub const response = await fetch( `${BLOG_BASE_URL}/manifests/page-${String(pageNum)}.json`, { - cache: "no-cache", + cache: "no-store", }, ); @@ -111,9 +42,7 @@ export async function fetchPage(pageNum: number): Promise { ); } - const data = (await response.json()) as PageManifest; - setCache(cacheKey, data); - return data; + return (await response.json()) as PageManifest; } /** @@ -122,30 +51,22 @@ export async function fetchPage(pageNum: number): Promise { export async function fetchPostMetadata( slug: string, ): Promise { - const cacheKey = `blog-metadata-${slug}-v1`; - - // Try cache first - const cached = getCached(cacheKey); - if (cached) { - return cached; - } - - // Fetch from GitHub try { - const response = await fetch( - `${BLOG_BASE_URL}/manifests/metadata/${slug}.json`, - { - cache: "no-cache", - }, - ); + // In dev, add timestamp to URL to bust cache (avoids CORS preflight issues) + const isDev = import.meta.env.DEV; + const url = isDev + ? `${BLOG_BASE_URL}/manifests/metadata/${slug}.json?t=${Date.now().toString()}` + : `${BLOG_BASE_URL}/manifests/metadata/${slug}.json`; + + const response = await fetch(url, { + cache: "no-store", + }); if (!response.ok) { return null; } - const data = (await response.json()) as BlogMetadata; - setCache(cacheKey, data); - return data; + return (await response.json()) as BlogMetadata; } catch (error) { console.error(`Error fetching metadata for ${slug}:`, error); return null; @@ -156,63 +77,19 @@ export async function fetchPostMetadata( * Fetch MDX content for a specific post */ export async function fetchPostContent(slug: string): Promise { - const cacheKey = `blog-content-${slug}-v1`; - - // Try cache first - const cached = getCached(cacheKey); - if (cached) { - return cached; - } - - // Fetch from GitHub - const response = await fetch(`${BLOG_BASE_URL}/posts/${slug}.mdx`, { - cache: "no-cache", + // In dev, add timestamp to URL to bust cache (avoids CORS preflight issues) + const isDev = import.meta.env.DEV; + const url = isDev + ? `${BLOG_BASE_URL}/posts/${slug}.mdx?t=${Date.now().toString()}` + : `${BLOG_BASE_URL}/posts/${slug}.mdx`; + + const response = await fetch(url, { + cache: "no-store", }); if (!response.ok) { throw new Error(`Failed to fetch post ${slug}: ${response.statusText}`); } - const content = await response.text(); - setCache(cacheKey, content); - return content; -} - -/** - * Clear all blog-related caches - * Useful for forcing a refresh - */ -export function clearBlogCache(): void { - const keys = Object.keys(localStorage); - keys.forEach((key) => { - if (key.startsWith("blog-")) { - localStorage.removeItem(key); - } - }); -} - -/** - * Get cache statistics for debugging - */ -export function getCacheStats(): { - totalEntries: number; - totalSize: number; - entries: string[]; -} { - const keys = Object.keys(localStorage); - const blogKeys = keys.filter((key) => key.startsWith("blog-")); - - let totalSize = 0; - blogKeys.forEach((key) => { - const item = localStorage.getItem(key); - if (item) { - totalSize += item.length; - } - }); - - return { - totalEntries: blogKeys.length, - totalSize, - entries: blogKeys, - }; + return await response.text(); } diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 650477f..30eee9e 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,5 +1,3 @@ -import type { MermaidConfig } from "mermaid"; - export const URL_RESUME = "/"; export const URL_BLOG = "/blog"; @@ -10,67 +8,3 @@ export const BLOG_REPO = "cagesthrottleus/cagesthrottleus.github.io"; export const BLOG_BRANCH = "blog"; export const BLOG_BASE_URL = `https://raw.githubusercontent.com/${BLOG_REPO}/${BLOG_BRANCH}`; export const BLOG_CACHE_DURATION = 5 * 60 * 1000; // 5 minutes in milliseconds - -/** - * Mermaid diagram configuration - * Cold War classified document theme - */ -export const MERMAID_CONFIG: MermaidConfig = { - startOnLoad: false, - theme: "dark", - themeVariables: { - // Cold War Classified Color Palette - primaryColor: "#dc2626", // classified-500 red - primaryTextColor: "#ffffff", - primaryBorderColor: "#dc2626", - lineColor: "#22c55e", // terminal-500 green - secondaryColor: "#3b82f6", // steel-500 blue - tertiaryColor: "#f59e0b", // warning-500 amber - background: "#0a0a0a", // dark-primary - mainBkg: "rgba(220, 38, 38, 0.05)", - secondBkg: "rgba(59, 130, 246, 0.05)", - border1: "#dc2626", - border2: "#22c55e", - arrowheadColor: "#22c55e", - fontFamily: "JetBrains Mono, monospace", - fontSize: "14px", - textColor: "#e8e8e8", // gray-200 - nodeBorder: "#dc2626", - clusterBkg: "rgba(34, 197, 94, 0.05)", - clusterBorder: "#22c55e", - defaultLinkColor: "#22c55e", - titleColor: "#ffffff", - edgeLabelBackground: "#0a0a0a", - nodeTextColor: "#ffffff", - // Flowchart specific - nodeBackground: "rgba(220, 38, 38, 0.1)", - nodeForeground: "#ffffff", - // Sequence diagram specific - actorBorder: "#dc2626", - actorBkg: "rgba(220, 38, 38, 0.1)", - actorTextColor: "#ffffff", - actorLineColor: "#22c55e", - signalColor: "#e8e8e8", - signalTextColor: "#e8e8e8", - labelBoxBkgColor: "rgba(59, 130, 246, 0.1)", - labelBoxBorderColor: "#3b82f6", - labelTextColor: "#ffffff", - // Git graph specific - git0: "#dc2626", - git1: "#22c55e", - git2: "#3b82f6", - git3: "#f59e0b", - git4: "#60a5fa", - git5: "#fbbf24", - git6: "#4ade80", - git7: "#ff3838", - commitLabelColor: "#ffffff", - commitLabelBackground: "rgba(220, 38, 38, 0.2)", - }, - securityLevel: "loose", - flowchart: { - htmlLabels: true, - curve: "basis", - padding: 15, - }, -};