diff --git a/.cursor/rules/alexandria.mdc b/.cursor/rules/alexandria.mdc index f45fffd..15ee6d0 100644 --- a/.cursor/rules/alexandria.mdc +++ b/.cursor/rules/alexandria.mdc @@ -9,11 +9,7 @@ You are senior full-stack software engineer with 20 years of experience writing ## Project Overview -Alexandria is a Nostr project written in Svelte 5 and SvelteKit 2. It is a web app for reading, commenting on, and publishing books, blogs, and other long-form content stored on Nostr relays. It revolves around breaking long AsciiDoc documents into Nostr events, with each event containing a paragraph or so of text from the document. These individual content events are organized by index events into publications. An index contains an ordered list of references to other index events or content events, forming a tree. - -### Reader Features - -In reader mode, Alexandria loads a document tree from a root publication index event. The AsciiDoc text content of the various content events, along with headers specified by tags in the index events, is composed and rendered as a single document from the user's point of view. +Alexandria is a Nostr project written in Svelte 5 and SvelteKit 2. It is a web app for reading, commenting on, and publishing books, blogs, and other long-form content stored on Nostr relays. ### Tech Stack @@ -36,14 +32,33 @@ When responding to prompts, adhere to the following rules: - Avoid proposing code edits unless I specifically tell you to do so. - When giving examples from my codebase, include the file name and line numbers so I can find the relevant code easily. -## Code Style +## Coding Guidelines + +### Prime Directive + +NEVER assume developer intent. If you are unsure about something, ALWAYS stop and ask the developer for clarification before proceeding. + +### AI Anchor Comments -Observe the following style guidelines when writing code: +- Use anchor comments prefixed with `AI-NOTE:`, `AI-TODO:`, or `AI-QUESTION:` to share context between AI agents and developers across time. + - Use all-caps prefixes. + - Also _read_ (but do not write) variants of this format that begin with `AI-:` where `` is some date in `MM/DD/YYYY` format. Anchor comments with this format are used by developers to record context. +- **Important:** Before scanning files, ALWAYS search first for `AI-` anchor comments in relevant subdirectories. +- ALWAYS update relevant anchor comments when modifying associated code. +- NEVER remove `AI-` comments unless the developer explicitly instructs it. +- Add new anchor comments as relevant when: + - Code is unusually complex. + - Code is critical to security, performance, or functionality. + - Code is confusing. + - Code could have a bug. ### General Guidance +- Before writing any code, ALWAYS search the codebase for relevant anchor comments. +- Whenever updating code, ALWAYS update relevant anchor comments. +- Prefer to use Deno to manage dependencies, build the project, and run tests. - Use snake_case names for plain TypeScript files. -- Use comments sparingly; code should be self-documenting. +- Use comments sparingly; aim to make code readable and self-documenting. ### JavaScript/TypeScript diff --git a/doc/settings_panel.org b/doc/settings_panel.org new file mode 100644 index 0000000..3730929 --- /dev/null +++ b/doc/settings_panel.org @@ -0,0 +1,124 @@ +* Settings Panel Documentation + +** Overview +The settings panel controls how events are fetched and displayed in the visualization. It has several sections that work together to create an efficient and user-friendly experience. + +** Event Types Configuration + +*** Purpose +Controls which types of Nostr events are fetched and how many of each type. + +*** Key Event Types +- *Kind 30040* (Index Events): Publication indices +- *Kind 30041* (Content Events): Publication content +- *Kind 30818* (Content Events): Alternative content format +- *Kind 30023* (Content Events): Alternative content format + +*** How Limits Work +Each event kind has a limit number that controls different things: + +**** For Kind 0 (Profiles) +- Limit controls how many profiles to fetch from discovered pubkeys +- These profiles are used for: + - Displaying names instead of pubkeys + - Showing profile pictures in tooltips + - When "People" tag anchors are selected, this limit controls how many people anchors to display + +**** For Kind 3 (Follow Lists) +- =limit = 1=: Only fetch the current user's follow list +- =limit > 1=: Fetch the user's follow list PLUS (limit-1) follow lists from people they follow +- The depth selector controls traversal: + - =Direct= (0): Just the immediate follows + - =2 degrees= (1): Follows of follows + - =3 degrees= (2): Three levels deep + +**** For Kind 30040/30041/30818 +- Limit controls maximum number of these events to fetch + +** Tag Anchors + +*** What Are Tag Anchors? +Tag anchors are special nodes in the graph that act as gravity points for events sharing common attributes. They help organize the visualization by grouping related content. + +*** Tag Types Available +- *Hashtags* (t): Groups events by hashtag +- *Authors*: Groups events by author +- *People* (p): Shows people from follow lists as anchor points +- *Event References* (e): Groups events that reference each other +- *Titles*: Groups events by title +- *Summaries*: Groups events by summary + +*** How People Tag Anchors Work +When "People" is selected as the tag type: + +1. The system looks at all loaded follow lists (kind 3 events) +2. Extracts all pubkeys (people) from those follow lists +3. Creates tag anchors for those people (up to the kind 0 limit) +4. Connects each person anchor to: + - Events they authored (where pubkey matches) + - Events where they're mentioned in "p" tags + +*** Display Limiting and Auto-Disable +- Tag anchors are created for ALL discovered tags +- But only displayed up to the configured limit +- When > 20 tag anchors exist, they're all auto-disabled +- Users can selectively enable specific anchors +- The legend becomes scrollable for many anchors + +*** "Only show people with publications" Checkbox +When checked (default): +- Only shows people who have events in the current visualization + +When unchecked: +- Shows ALL people from follow lists, even if they have no events displayed +- Useful for seeing your complete social graph + +** Display Limits Section + +*** Max Publication Indices (30040) +Controls display filtering for publication indices after they're fetched. + +*** Max Events per Index +Limits how many content events to show per publication index. + +*** Fetch if not found +When enabled, automatically fetches missing referenced events. + +** Graph Traversal Section + +*** Search through already fetched +When enabled, tag expansion only searches through events already loaded (more efficient). + +*** Append mode +When enabled, new fetches add to the existing graph instead of replacing it. + +** Current Implementation Questions + +1. *Profile Fetching*: Should we fetch profiles for: + - Only event authors? + - All pubkeys in follow lists? + - All pubkeys mentioned anywhere? + +2. *People Tag Anchors*: Should they connect to: + - Only events where the person is tagged with "p"? + - Events they authored? + - Both? + +3. *Display Limits*: Should limits control: + - How many to fetch from relays? + - How many to display (fetch all, display subset)? + - Both with separate controls? + +4. *Auto-disable Threshold*: Is 20 the right number for auto-disabling tag anchors? + +** Ideal User Flow + +1. User loads the visualization +2. Their follow list is fetched (kind 3, limit 1) +3. Profiles are fetched for people they follow (kind 0, respecting limit) +4. Publications are fetched (kind 30040/30041/30818) +5. User enables "People" tag anchors +6. Sees their follows as anchor points +7. Can see which follows have authored content +8. Can selectively enable/disable specific people +9. Can increase limits to see more content/people diff --git a/package-lock.json b/package-lock.json index ad65282..ed96156 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -73,6 +74,7 @@ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -85,6 +87,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@asciidoctor/cli/-/cli-4.0.0.tgz", "integrity": "sha512-x2T9gW42921Zd90juEagtbViPZHNP2MWf0+6rJEkOzW7E9m3TGJtz+Guye9J0gwrpZsTMGCpfYMQy1We3X7osg==", + "license": "MIT", "dependencies": { "yargs": "17.3.1" }, @@ -104,6 +107,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@asciidoctor/core/-/core-3.0.4.tgz", "integrity": "sha512-41SDMi7iRRBViPe0L6VWFTe55bv6HEOJeRqMj5+E5wB1YPdUPuTucL4UAESPZM6OWmn4t/5qM5LusXomFUVwVQ==", + "license": "MIT", "dependencies": { "@asciidoctor/opal-runtime": "3.0.1", "unxhr": "1.2.0" @@ -117,6 +121,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@asciidoctor/opal-runtime/-/opal-runtime-3.0.1.tgz", "integrity": "sha512-iW7ACahOG0zZft4A/4CqDcc7JX+fWRNjV5tFAVkNCzwZD+EnFolPaUOPYt8jzadc0+Bgd80cQTtRMQnaaV1kkg==", + "license": "MIT", "dependencies": { "glob": "8.1.0", "unxhr": "1.2.0" @@ -129,6 +134,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -137,6 +143,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -145,6 +152,7 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "license": "MIT", "dependencies": { "@babel/types": "^7.28.0" }, @@ -159,6 +167,7 @@ "version": "7.28.2", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -175,6 +184,7 @@ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -191,6 +201,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -207,6 +218,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -223,6 +235,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -239,6 +252,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -255,6 +269,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -271,6 +286,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -287,6 +303,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -303,6 +320,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -319,6 +337,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -335,6 +354,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -351,6 +371,7 @@ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -367,6 +388,7 @@ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -383,6 +405,7 @@ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -399,6 +422,7 @@ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -415,6 +439,7 @@ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -431,6 +456,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -447,6 +473,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -463,6 +490,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -479,6 +507,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -495,6 +524,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -511,6 +541,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openharmony" @@ -527,6 +558,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -543,6 +575,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -559,6 +592,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -575,6 +609,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -588,6 +623,7 @@ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -606,6 +642,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -618,6 +655,7 @@ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -628,6 +666,7 @@ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, + "license": "Apache-2.0", "peer": true, "dependencies": { "@eslint/object-schema": "^2.1.6", @@ -643,6 +682,7 @@ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "dev": true, + "license": "Apache-2.0", "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -653,6 +693,7 @@ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "dev": true, + "license": "Apache-2.0", "peer": true, "dependencies": { "@types/json-schema": "^7.0.15" @@ -666,6 +707,7 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "ajv": "^6.12.4", @@ -690,6 +732,7 @@ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -703,6 +746,7 @@ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, + "license": "Apache-2.0", "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -713,6 +757,7 @@ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, + "license": "Apache-2.0", "peer": true, "dependencies": { "@eslint/core": "^0.15.1", @@ -723,21 +768,23 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz", - "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", "dev": true, + "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz", - "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.3.tgz", + "integrity": "sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==", "dev": true, + "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.2", + "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, @@ -745,13 +792,15 @@ "version": "0.2.10", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, + "license": "Apache-2.0", "peer": true, "engines": { "node": ">=18.18.0" @@ -762,6 +811,7 @@ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, + "license": "Apache-2.0", "peer": true, "dependencies": { "@humanfs/core": "^0.19.1", @@ -776,6 +826,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", "dev": true, + "license": "Apache-2.0", "peer": true, "engines": { "node": ">=18.18" @@ -790,6 +841,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "peer": true, "engines": { "node": ">=12.22" @@ -804,6 +856,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, + "license": "Apache-2.0", "peer": true, "engines": { "node": ">=18.18" @@ -817,6 +870,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -833,6 +887,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -844,6 +899,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -854,12 +910,14 @@ "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -876,6 +934,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -890,6 +949,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -906,6 +966,7 @@ "version": "0.3.12", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" @@ -915,6 +976,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -922,12 +984,14 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==" + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.29", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -937,14 +1001,16 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz", "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==", + "license": "MIT", "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@noble/curves": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.4.tgz", - "integrity": "sha512-2bKONnuM53lINoDrSmK8qP8W271ms7pygDhZt4SiLOoLwBtoHqeCFi6RG42V8zd3mLHuJFhU/Bmaqo4nX0/kBw==", + "version": "1.9.6", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.6.tgz", + "integrity": "sha512-GIKz/j99FRthB8icyJQA51E8Uk5hXmdyThjgQXRKiv9h0zeRlzSCLIzFw6K1LotZ3XuB7yzlf76qk7uBmTdFqA==", + "license": "MIT", "dependencies": { "@noble/hashes": "1.8.0" }, @@ -959,6 +1025,7 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", "engines": { "node": "^14.21.3 || >=16" }, @@ -970,6 +1037,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.3.0.tgz", "integrity": "sha512-0TQed2gcBbIrh7Ccyw+y/uZQvbJwm7Ao4scBUxqpBCcsOlZG0O4KGfjtNAy/li4W8n1xt3dxrwJ0beZ2h2G6Kw==", + "license": "MIT", "funding": { "url": "https://paulmillr.com/funding/" } @@ -978,6 +1046,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -990,6 +1059,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", "engines": { "node": ">= 8" } @@ -998,6 +1068,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1007,9 +1078,10 @@ } }, "node_modules/@nostr-dev-kit/ndk": { - "version": "2.14.32", - "resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-2.14.32.tgz", - "integrity": "sha512-LUBO35RCB9/emBYsXNDece7m/WO2rGYR8j4SD0Crb3z8GcKTJq6P8OjpZ6+Kr+sLNo8N0uL07XxtAvEBnp2OqQ==", + "version": "2.14.33", + "resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-2.14.33.tgz", + "integrity": "sha512-akiafJZj4ZAAYse+qNSjrx6Yg4Y2gB4UyMlo6I30ITVikRAtgPejXgtLGmjWCcgtf56b9g79AikAr3IZtr1pLA==", + "license": "MIT", "dependencies": { "@noble/curves": "^1.6.0", "@noble/hashes": "^1.5.0", @@ -1028,11 +1100,12 @@ } }, "node_modules/@nostr-dev-kit/ndk-cache-dexie": { - "version": "2.6.33", - "resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk-cache-dexie/-/ndk-cache-dexie-2.6.33.tgz", - "integrity": "sha512-JzUD5cuJbGQDUXYuW1530vy347Kk3AhdtvPO8tL6kFpV3KzGt/QPZ0SHxcjMhJdf7r6cAIpCEWj9oUlStr0gsg==", + "version": "2.6.34", + "resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk-cache-dexie/-/ndk-cache-dexie-2.6.34.tgz", + "integrity": "sha512-NFk9I7E/eXIevLDnjyZHHwxdL4E891KVGlkr0k07CItoIf8A2cxtZJ7Pe4Ei5fe49nipL9iA+sEmOq9xveLR7g==", + "license": "MIT", "dependencies": { - "@nostr-dev-kit/ndk": "2.14.32", + "@nostr-dev-kit/ndk": "2.14.33", "debug": "^4.3.7", "dexie": "^4.0.8", "nostr-tools": "^2.4.0", @@ -1043,18 +1116,20 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", "optional": true, "engines": { "node": ">=14" } }, "node_modules/@playwright/test": { - "version": "1.54.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz", - "integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==", + "version": "1.54.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.2.tgz", + "integrity": "sha512-A+znathYxPf+72riFd1r1ovOLqsIIB0jKIoPjyK2kqEIe30/6jF6BC7QNluHuwUmsD2tv1XZVugN8GqfTMOxsA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright": "1.54.1" + "playwright": "1.54.2" }, "bin": { "playwright": "cli.js" @@ -1067,12 +1142,14 @@ "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -1083,6 +1160,7 @@ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.6.tgz", "integrity": "sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", @@ -1109,6 +1187,7 @@ "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^5.1.0" }, @@ -1129,6 +1208,7 @@ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", @@ -1153,6 +1233,7 @@ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", @@ -1171,260 +1252,280 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.3.tgz", - "integrity": "sha512-8oQkCTve4H4B4JpmD2FV7fV2ZPTxJHN//bRhCqPUU8v6c5APlxteAXyc7BFaEb4aGpUzrPLU4PoAcGhwmRzZTA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.3.tgz", - "integrity": "sha512-StOsmdXHU2hx3UFTTs6yYxCSwSIgLsfjUBICXyWj625M32OOjakXlaZuGKL+jA3Nvv35+hMxrm/64eCoT07SYQ==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.3.tgz", - "integrity": "sha512-6CfLF3eqKhCdhK0GUnR5ZS99OFz+dtOeB/uePznLKxjCsk5QjT/V0eSEBb4vj+o/ri3i35MseSEQHCLLAgClVw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.3.tgz", - "integrity": "sha512-QLWyWmAJG9elNTNLdcSXUT/M+J7DhEmvs1XPHYcgYkse3UHf9iWTJ+yTPlKMIetiQnNi+cNp+gY4gvjDpREfKw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.3.tgz", - "integrity": "sha512-ZOvBq+5nL0yrZIEo1eq6r7MPvkJ8kC1XATS/yHvcq3WbDNKNKBQ1uIF4hicyzDMoJt72G+sn1nKsFXpifZyRDA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.3.tgz", - "integrity": "sha512-AYvGR07wecEnyYSovyJ71pTOulbNvsrpRpK6i/IM1b0UGX1vFx51afYuPYPxnvE9aCl5xPnhQicEvdIMxClRgQ==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.3.tgz", - "integrity": "sha512-Yx8Cp38tfRRToVLuIWzBHV25/QPzpUreOPIiUuNV7KahNPurYg2pYQ4l7aYnvpvklO1riX4643bXLvDsYSBIrA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.3.tgz", - "integrity": "sha512-4dIYRNxlXGDKnO6qgcda6LxnObPO6r1OBU9HG8F9pAnHHLtfbiOqCzDvkeHknx+5mfFVH4tWOl+h+cHylwsPWA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.3.tgz", - "integrity": "sha512-M6uVlWKmhLN7LguLDu6396K1W5IBlAaRonjlHQgc3s4dOGceu0FeBuvbXiUPYvup/6b5Ln7IEX7XNm68DN4vrg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.3.tgz", - "integrity": "sha512-emaYiOTQJUd6fC9a6jcw9zIWtzaUiuBC+vomggaM4In2iOra/lA6IMHlqZqQZr08NYXrOPMVigreLMeSAwv3Uw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.3.tgz", - "integrity": "sha512-3P77T5AQ4UfVRJSrTKLiUZDJ6XsxeP80027bp6mOFh8sevSD038mYuIYFiUtrSJxxgFb+NgRJFF9oIa0rlUsmg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.45.3.tgz", - "integrity": "sha512-/VPH3ZVeSlmCBPhZdx/+4dMXDjaGMhDsWOBo9EwSkGbw2+OAqaslL53Ao2OqCxR0GgYjmmssJ+OoG+qYGE7IBg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.3.tgz", - "integrity": "sha512-Hs5if0PjROl1MGMmZX3xMAIfqcGxQE2SJWUr/CpDQsOQn43Wq4IvXXxUMWtiY/BrzdqCCJlRgJ5DKxzS3qWkCw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.3.tgz", - "integrity": "sha512-Qm0WOwh3Lk388+HJFl1ILGbd2iOoQf6yl4fdGqOjBzEA+5JYbLcwd+sGsZjs5pkt8Cr/1G42EiXmlRp9ZeTvFA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.3.tgz", - "integrity": "sha512-VJdknTaYw+TqXzlh9c7vaVMh/fV2sU8Khfk4a9vAdYXJawpjf6z3U1k7vDWx2IQ9ZOPoOPxgVpDfYOYhxD7QUA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.3.tgz", - "integrity": "sha512-SUDXU5YabLAMl86FpupSQQEWzVG8X0HM+Q/famnJusbPiUgQnTGuSxtxg4UAYgv1ZmRV1nioYYXsgtSokU/7+Q==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.3.tgz", - "integrity": "sha512-ezmqknOUFgZMN6wW+Avlo4sXF3Frswd+ncrwMz4duyZ5Eqd+dAYgJ+A1MY+12LNZ7XDhCiijJceueYvtnzdviw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.3.tgz", - "integrity": "sha512-1YfXoUEE++gIW66zNB9Twd0Ua5xCXpfYppFUxVT/Io5ZT3fO6Se+C/Jvmh3usaIHHyi53t3kpfjydO2GAy5eBA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.3.tgz", - "integrity": "sha512-Iok2YA3PvC163rVZf2Zy81A0g88IUcSPeU5pOilcbICXre2EP1mxn1Db/l09Z/SK1vdSLtpJXAnwGuMOyf5O9g==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.3.tgz", - "integrity": "sha512-HwHCH5GQTOeGYP5wBEBXFVhfQecwRl24Rugoqhh8YwGarsU09bHhOKuqlyW4ZolZCan3eTUax7UJbGSmKSM51A==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1434,6 +1535,7 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", "funding": { "url": "https://paulmillr.com/funding/" } @@ -1442,6 +1544,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz", "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", + "license": "MIT", "dependencies": { "@noble/curves": "~1.1.0", "@noble/hashes": "~1.3.1", @@ -1455,6 +1558,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "license": "MIT", "dependencies": { "@noble/hashes": "1.3.1" }, @@ -1466,6 +1570,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "license": "MIT", "engines": { "node": ">= 16" }, @@ -1477,6 +1582,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "license": "MIT", "engines": { "node": ">= 16" }, @@ -1488,6 +1594,7 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "license": "MIT", "funding": { "url": "https://paulmillr.com/funding/" } @@ -1496,6 +1603,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "license": "MIT", "dependencies": { "@noble/hashes": "~1.3.0", "@scure/base": "~1.1.0" @@ -1508,6 +1616,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "license": "MIT", "engines": { "node": ">= 16" }, @@ -1519,6 +1628,7 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "license": "MIT", "funding": { "url": "https://paulmillr.com/funding/" } @@ -1527,6 +1637,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -1534,11 +1645,19 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@sveltejs/acorn-typescript": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^8.9.0" } @@ -1548,6 +1667,7 @@ "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-6.0.1.tgz", "integrity": "sha512-mcWud3pYGPWM2Pphdj8G9Qiq24nZ8L4LB7coCUckUEy5Y7wOWGJ/enaZ4AtJTcSm5dNK1rIkBRoqt+ae4zlxcQ==", "dev": true, + "license": "MIT", "peerDependencies": { "@sveltejs/kit": "^2.0.0" } @@ -1557,6 +1677,7 @@ "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.13.tgz", "integrity": "sha512-yS2TVFmIrxjGhYaV5/iIUrJ3mJl6zjaYn0lBD70vTLnYvJeqf3cjvLXeXCUCuYinhSBoyF4DpfGla49BnIy7sQ==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/plugin-commonjs": "^28.0.1", "@rollup/plugin-json": "^6.1.0", @@ -1572,16 +1693,19 @@ "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz", "integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==", "dev": true, + "license": "MIT", "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "node_modules/@sveltejs/kit": { - "version": "2.26.1", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.26.1.tgz", - "integrity": "sha512-FwDhHAoXYUGnYndrrEzEYcKdYWpSoRKq4kli29oMe83hLri4/DOGQk3xUgwjDo0LKpSmj5M/Sj29/Ug3wO0Cbg==", + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.27.0.tgz", + "integrity": "sha512-pEX1Z2Km8tqmkni+ykIIou+ojp/7gb3M9tpllN5nDWNo9zlI0dI8/hDKFyBwQvb4jYR+EyLriFtrmgJ6GvbnBA==", "dev": true, + "license": "MIT", "dependencies": { + "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", @@ -1612,6 +1736,7 @@ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.1.0.tgz", "integrity": "sha512-+U6lz1wvGEG/BvQyL4z/flyNdQ9xDNv5vrh+vWBWTHaebqT0c9RNggpZTo/XSPoHsSCWBlYaTlRX8pZ9GATXCw==", "dev": true, + "license": "MIT", "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0-next.1", "debug": "^4.4.1", @@ -1633,6 +1758,7 @@ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.0.tgz", "integrity": "sha512-iwQ8Z4ET6ZFSt/gC+tVfcsSBHwsqc6RumSaiLUkAurW3BCpJam65cmHw0oOlDMTO0u+PZi9hilBRYN+LZNHTUQ==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.4.1" }, @@ -1649,6 +1775,7 @@ "version": "0.5.10", "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", + "license": "MIT", "dependencies": { "mini-svg-data-uri": "^1.2.3" }, @@ -1660,6 +1787,7 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==", + "license": "MIT", "dependencies": { "lodash.castarray": "^4.4.0", "lodash.isplainobject": "^4.0.6", @@ -1675,6 +1803,7 @@ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", "dev": true, + "license": "MIT", "dependencies": { "@types/deep-eql": "*" } @@ -1683,13 +1812,15 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "dev": true + "dev": true, + "license": "MIT" }, "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==", "dev": true, + "license": "MIT", "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", @@ -1727,13 +1858,15 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", - "dev": true + "dev": true, + "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==", "dev": true, + "license": "MIT", "dependencies": { "@types/d3-selection": "*" } @@ -1743,6 +1876,7 @@ "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", "dev": true, + "license": "MIT", "dependencies": { "@types/d3-selection": "*" } @@ -1751,19 +1885,22 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", - "dev": true + "dev": true, + "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==", - "dev": true + "dev": true, + "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==", "dev": true, + "license": "MIT", "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" @@ -1773,19 +1910,22 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/d3-dispatch": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", - "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==", - "dev": true + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "dev": true, + "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==", "dev": true, + "license": "MIT", "dependencies": { "@types/d3-selection": "*" } @@ -1794,19 +1934,22 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", - "dev": true + "dev": true, + "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==", - "dev": true + "dev": true, + "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==", "dev": true, + "license": "MIT", "dependencies": { "@types/d3-dsv": "*" } @@ -1815,19 +1958,22 @@ "version": "3.0.10", "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", - "dev": true + "dev": true, + "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==", - "dev": true + "dev": true, + "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==", "dev": true, + "license": "MIT", "dependencies": { "@types/geojson": "*" } @@ -1836,13 +1982,15 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", - "dev": true + "dev": true, + "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==", "dev": true, + "license": "MIT", "dependencies": { "@types/d3-color": "*" } @@ -1851,31 +1999,36 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "dev": true + "dev": true, + "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==", - "dev": true + "dev": true, + "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==", - "dev": true + "dev": true, + "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==", - "dev": true + "dev": true, + "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==", "dev": true, + "license": "MIT", "dependencies": { "@types/d3-time": "*" } @@ -1884,19 +2037,22 @@ "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==", - "dev": true + "dev": true, + "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==", - "dev": true + "dev": true, + "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==", "dev": true, + "license": "MIT", "dependencies": { "@types/d3-path": "*" } @@ -1905,25 +2061,29 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "dev": true + "dev": true, + "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==", - "dev": true + "dev": true, + "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==", - "dev": true + "dev": true, + "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==", "dev": true, + "license": "MIT", "dependencies": { "@types/d3-selection": "*" } @@ -1933,6 +2093,7 @@ "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", "dev": true, + "license": "MIT", "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" @@ -1942,44 +2103,51 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true + "dev": true, + "license": "MIT" }, "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==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/he": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/he/-/he-1.2.3.tgz", "integrity": "sha512-q67/qwlxblDzEDvzHhVkwc1gzVWxaNxeyHUBF4xElrvjL11O+Ytze+1fGpBHlr/H9myiBUaUXNnNPmBHxxfAcA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@types/mathjax": { "version": "0.0.40", "resolved": "https://registry.npmjs.org/@types/mathjax/-/mathjax-0.0.40.tgz", "integrity": "sha512-rHusx08LCg92WJxrsM3SPjvLTSvK5C+gealtSuhKbEOcUZfWlwigaFoPLf6Dfxhg4oryN5qP9Sj7zOQ4HYXINw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "24.1.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~7.8.0" } @@ -1989,6 +2157,7 @@ "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz", "integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -1997,13 +2166,15 @@ "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, + "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", @@ -2020,6 +2191,7 @@ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", "dev": true, + "license": "MIT", "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", @@ -2046,6 +2218,7 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } @@ -2055,6 +2228,7 @@ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", "dev": true, + "license": "MIT", "dependencies": { "tinyrainbow": "^2.0.0" }, @@ -2067,6 +2241,7 @@ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, + "license": "MIT", "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", @@ -2081,6 +2256,7 @@ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, + "license": "MIT", "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", @@ -2095,6 +2271,7 @@ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, + "license": "MIT", "dependencies": { "tinyspy": "^4.0.3" }, @@ -2107,6 +2284,7 @@ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, + "license": "MIT", "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", @@ -2120,18 +2298,21 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/a-sync-waterfall": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", - "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==" + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", + "license": "MIT" }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -2144,6 +2325,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -2153,6 +2335,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", @@ -2169,6 +2352,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -2177,6 +2361,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -2190,12 +2375,14 @@ "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -2208,6 +2395,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -2220,6 +2408,7 @@ "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.54.1.tgz", "integrity": "sha512-E4et0h/J1U3r3EwS/WlqJCQIbepKbp6wGUmaAwJOMjHUP4Ci0gxanLa7FR3okx6p9coi4st6J853/Cb1NP0vpA==", "dev": true, + "license": "MIT", "dependencies": { "@yr/monotone-cubic-spline": "^1.0.3", "svg.draggable.js": "^2.2.2", @@ -2233,13 +2422,15 @@ "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, + "license": "Python-2.0", "peer": true }, "node_modules/aria-query": { @@ -2247,6 +2438,7 @@ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">= 0.4" } @@ -2254,12 +2446,14 @@ "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" }, "node_modules/asciidoctor": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/asciidoctor/-/asciidoctor-3.0.4.tgz", "integrity": "sha512-hIc0Bx73wePxtic+vWBHOIgMfKSNiCmRz7BBfkyykXATrw20YGd5a3CozCHvqEPH+Wxp5qKD4aBsgtokez8nEA==", + "license": "MIT", "dependencies": { "@asciidoctor/cli": "4.0.0", "@asciidoctor/core": "3.0.4", @@ -2280,13 +2474,15 @@ "node_modules/assert-never": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.4.0.tgz", - "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==" + "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==", + "license": "MIT" }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" } @@ -2294,7 +2490,8 @@ "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" }, "node_modules/autoprefixer": { "version": "10.4.21", @@ -2315,6 +2512,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -2338,6 +2536,7 @@ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">= 0.4" } @@ -2346,6 +2545,7 @@ "version": "3.0.0-canary-5", "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "license": "MIT", "dependencies": { "@babel/types": "^7.9.6" }, @@ -2356,17 +2556,20 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" }, "node_modules/bech32": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", - "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", + "license": "MIT" }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -2378,6 +2581,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2387,6 +2591,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -2413,6 +2618,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", @@ -2431,6 +2637,7 @@ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2439,6 +2646,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -2451,6 +2659,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -2467,6 +2676,7 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=6" @@ -2476,6 +2686,7 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", "engines": { "node": ">=6" } @@ -2484,14 +2695,15 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", "engines": { "node": ">= 6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001727", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", - "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "version": "1.0.30001731", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz", + "integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==", "dev": true, "funding": [ { @@ -2506,13 +2718,15 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chai": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz", "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==", "dev": true, + "license": "MIT", "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", @@ -2528,6 +2742,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2543,6 +2758,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "license": "MIT", "engines": { "node": ">=10" } @@ -2551,6 +2767,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "license": "MIT", "dependencies": { "is-regex": "^1.0.3" } @@ -2560,48 +2777,32 @@ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 16" } }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" } }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -2613,6 +2814,7 @@ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -2621,6 +2823,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -2631,12 +2834,14 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" }, "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" } @@ -2645,17 +2850,20 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" }, "node_modules/constantinople": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "license": "MIT", "dependencies": { "@babel/parser": "^7.6.0", "@babel/types": "^7.6.1" @@ -2666,6 +2874,7 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2674,6 +2883,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2687,6 +2897,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -2698,6 +2909,7 @@ "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", @@ -2738,6 +2950,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", "dependencies": { "internmap": "1 - 2" }, @@ -2749,6 +2962,7 @@ "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" } @@ -2757,6 +2971,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", @@ -2772,6 +2987,7 @@ "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" }, @@ -2783,6 +2999,7 @@ "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" } @@ -2791,6 +3008,7 @@ "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" }, @@ -2802,6 +3020,7 @@ "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" }, @@ -2813,6 +3032,7 @@ "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" } @@ -2821,6 +3041,7 @@ "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" @@ -2833,6 +3054,7 @@ "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", @@ -2857,6 +3079,7 @@ "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" } @@ -2865,6 +3088,7 @@ "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" }, @@ -2876,6 +3100,7 @@ "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", @@ -2889,6 +3114,7 @@ "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" } @@ -2897,6 +3123,7 @@ "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" }, @@ -2908,6 +3135,7 @@ "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" } @@ -2916,6 +3144,7 @@ "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" }, @@ -2927,6 +3156,7 @@ "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" } @@ -2935,6 +3165,7 @@ "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" } @@ -2943,6 +3174,7 @@ "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" } @@ -2951,6 +3183,7 @@ "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" } @@ -2959,6 +3192,7 @@ "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", @@ -2974,6 +3208,7 @@ "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" @@ -2986,6 +3221,7 @@ "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", "engines": { "node": ">=12" } @@ -2994,6 +3230,7 @@ "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" }, @@ -3005,6 +3242,7 @@ "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" }, @@ -3016,6 +3254,7 @@ "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" }, @@ -3027,6 +3266,7 @@ "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" } @@ -3035,6 +3275,7 @@ "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", @@ -3053,6 +3294,7 @@ "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", @@ -3068,6 +3310,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -3084,6 +3327,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -3093,6 +3337,7 @@ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3102,6 +3347,7 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/deepmerge": { @@ -3109,6 +3355,7 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -3117,6 +3364,7 @@ "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" } @@ -3125,37 +3373,44 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/dexie": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.11.tgz", - "integrity": "sha512-SOKO002EqlvBYYKQSew3iymBoN2EQ4BDw/3yprjh7kAfFzjBYkaMNa/pZvcA7HSWlcKSQb9XhPe3wKyQ0x4A8A==" + "integrity": "sha512-SOKO002EqlvBYYKQSew3iymBoN2EQ4BDw/3yprjh7kAfFzjBYkaMNa/pZvcA7HSWlcKSQb9XhPe3wKyQ0x4A8A==", + "license": "Apache-2.0" }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" }, "node_modules/dijkstrajs": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", - "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" }, "node_modules/doctypes": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", - "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==" + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", + "license": "MIT" }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -3168,12 +3423,14 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", "dependencies": { "jake": "^10.8.5" }, @@ -3185,25 +3442,29 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.191", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.191.tgz", - "integrity": "sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==", - "dev": true + "version": "1.5.194", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.194.tgz", + "integrity": "sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA==", + "dev": true, + "license": "ISC" }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/emojilib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", - "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==" + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "license": "MIT" }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -3212,6 +3473,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -3220,12 +3482,14 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -3239,6 +3503,7 @@ "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -3278,6 +3543,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -3287,6 +3553,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=10" @@ -3300,6 +3567,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -3361,6 +3629,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.11.0.tgz", "integrity": "sha512-KliWlkieHyEa65aQIkRwUFfHzT5Cn4u3BQQsu3KlkJOs7c1u7ryn84EWaOjEzilbKgttT4OfBURA8Uc4JBSQIw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.6.1", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -3394,6 +3663,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -3406,6 +3676,7 @@ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -3415,6 +3686,7 @@ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", "dev": true, + "license": "MIT", "dependencies": { "lilconfig": "^2.0.5", "yaml": "^1.10.2" @@ -3439,20 +3711,12 @@ } } }, - "node_modules/eslint-plugin-svelte/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -3469,6 +3733,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3480,13 +3745,15 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", @@ -3504,6 +3771,7 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", "peer": true, "dependencies": { "estraverse": "^5.1.0" @@ -3517,6 +3785,7 @@ "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } @@ -3526,6 +3795,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -3538,6 +3808,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -3546,13 +3817,15 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -3562,6 +3835,7 @@ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.0.0" } @@ -3571,12 +3845,14 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -3592,6 +3868,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -3604,6 +3881,7 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/fast-levenshtein": { @@ -3611,12 +3889,14 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -3626,6 +3906,7 @@ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, + "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -3640,6 +3921,7 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "flat-cache": "^4.0.0" @@ -3652,6 +3934,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "license": "Apache-2.0", "dependencies": { "minimatch": "^5.0.1" } @@ -3660,6 +3943,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -3668,6 +3952,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -3679,6 +3964,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -3691,6 +3977,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "locate-path": "^6.0.0", @@ -3708,6 +3995,7 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "flatted": "^3.2.9", @@ -3722,6 +4010,7 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, + "license": "ISC", "peer": true }, "node_modules/flowbite": { @@ -3729,6 +4018,7 @@ "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.5.2.tgz", "integrity": "sha512-kwFD3n8/YW4EG8GlY3Od9IoKND97kitO+/ejISHSqpn3vw2i5K/+ZI8Jm2V+KC4fGdnfi0XZ+TzYqQb4Q1LshA==", "dev": true, + "license": "MIT", "dependencies": { "@popperjs/core": "^2.9.3", "flowbite-datepicker": "^1.3.0", @@ -3740,6 +4030,7 @@ "resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.3.2.tgz", "integrity": "sha512-6Nfm0MCVX3mpaR7YSCjmEO2GO8CDt6CX8ZpQnGdeu03WUCWtEPQ/uy0PUiNtIJjJZWnX0Cm3H55MOhbD1g+E/g==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/plugin-node-resolve": "^15.2.3", "flowbite": "^2.0.0" @@ -3750,6 +4041,7 @@ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", @@ -3774,6 +4066,7 @@ "resolved": "https://registry.npmjs.org/flowbite-svelte/-/flowbite-svelte-0.48.6.tgz", "integrity": "sha512-/PmeR3ipHHvda8vVY9MZlymaRoJsk8VddEeoLzIygfYwJV68ey8gHuQPC1dq9J6NDCTE5+xOPtBiYUtVjCfvZw==", "dev": true, + "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.6.13", "apexcharts": "^3.54.1", @@ -3789,6 +4082,7 @@ "resolved": "https://registry.npmjs.org/flowbite-svelte-icons/-/flowbite-svelte-icons-2.1.1.tgz", "integrity": "sha512-VNNMcekjbM1bQEGgbdGsdYR9mRdTj/L0A5ba0P1tiFv5QB9GvbvJMABJoiD80eqpZUkfR2QVOmiZfgCwHicT/Q==", "dev": true, + "license": "MIT", "peerDependencies": { "svelte": "^5.0.0", "tailwind-merge": "^3.0.0" @@ -3799,6 +4093,7 @@ "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-3.1.2.tgz", "integrity": "sha512-MkwSgbbybCYgMC+go6Da5idEKUFfMqc/AmSjm/2ZbdmvoKf5frLPq/eIhXc9P+rC8t9boZtUXzHDgt5whZ6A/Q==", "dev": true, + "license": "MIT", "dependencies": { "@popperjs/core": "^2.9.3", "flowbite-datepicker": "^1.3.1", @@ -3810,6 +4105,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" @@ -3826,6 +4122,7 @@ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true, + "license": "MIT", "engines": { "node": "*" }, @@ -3837,13 +4134,15 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -3856,6 +4155,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3864,6 +4164,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -3872,6 +4173,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -3895,6 +4197,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -3908,6 +4211,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3926,6 +4230,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -3937,6 +4242,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -3945,6 +4251,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -3957,6 +4264,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=18" @@ -3969,6 +4277,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3980,6 +4289,7 @@ "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "license": "MIT", "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", @@ -4000,6 +4310,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -4008,6 +4319,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4019,6 +4331,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, @@ -4033,6 +4346,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -4044,6 +4358,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", "bin": { "he": "bin/he" } @@ -4052,6 +4367,7 @@ "version": "11.11.1", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", "engines": { "node": ">=12.0.0" } @@ -4060,6 +4376,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -4072,6 +4389,7 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">= 4" @@ -4082,6 +4400,7 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "parent-module": "^1.0.0", @@ -4099,6 +4418,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=0.8.19" @@ -4109,6 +4429,7 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -4117,12 +4438,14 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "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" } @@ -4131,6 +4454,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -4142,6 +4466,7 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, @@ -4156,6 +4481,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "license": "MIT", "dependencies": { "acorn": "^7.1.1", "object-assign": "^4.1.1" @@ -4165,6 +4491,7 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -4176,6 +4503,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4184,6 +4512,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", "engines": { "node": ">=8" } @@ -4192,6 +4521,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -4203,12 +4533,14 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -4216,13 +4548,15 @@ "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "license": "MIT" }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "*" } @@ -4231,6 +4565,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", @@ -4247,12 +4582,14 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -4267,6 +4604,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "license": "Apache-2.0", "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", @@ -4284,6 +4622,7 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", "bin": { "jiti": "bin/jiti.js" } @@ -4291,19 +4630,22 @@ "node_modules/js-stringify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", - "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==" + "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", + "license": "MIT" }, "node_modules/js-tokens": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "argparse": "^2.0.1" @@ -4317,6 +4659,7 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/json-schema-traverse": { @@ -4324,6 +4667,7 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/json-stable-stringify-without-jsonify": { @@ -4331,12 +4675,14 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/jstransformer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "license": "MIT", "dependencies": { "is-promise": "^2.0.0", "promise": "^7.0.1" @@ -4347,6 +4693,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "json-buffer": "3.0.1" @@ -4357,6 +4704,7 @@ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -4365,13 +4713,15 @@ "version": "0.37.0", "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "prelude-ls": "^1.2.1", @@ -4385,6 +4735,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/light-bolt11-decoder/-/light-bolt11-decoder-3.2.0.tgz", "integrity": "sha512-3QEofgiBOP4Ehs9BI+RkZdXZNtSys0nsJ6fyGeSiAGCBsMwHGUDS/JQlY/sTnWs91A2Nh0S9XXfA8Sy9g6QpuQ==", + "license": "MIT", "dependencies": { "@scure/base": "1.1.1" } @@ -4398,12 +4749,14 @@ "type": "individual", "url": "https://paulmillr.com/funding/" } - ] + ], + "license": "MIT" }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -4414,19 +4767,22 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" }, "node_modules/locate-character": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "p-locate": "^5.0.0" @@ -4441,34 +4797,40 @@ "node_modules/lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", - "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==" + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "license": "MIT" }, "node_modules/loupe": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.0.tgz", "integrity": "sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } @@ -4477,6 +4839,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -4485,6 +4848,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", "engines": { "node": ">= 8" } @@ -4493,6 +4857,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -4505,6 +4870,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -4516,6 +4882,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "license": "MIT", "bin": { "mini-svg-data-uri": "cli.js" } @@ -4524,6 +4891,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4535,6 +4903,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4543,6 +4912,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -4552,6 +4922,7 @@ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -4561,6 +4932,7 @@ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -4568,12 +4940,14 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -4590,6 +4964,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -4602,17 +4977,20 @@ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" }, "node_modules/node-emoji": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "license": "MIT", "dependencies": { "@sindresorhus/is": "^4.6.0", "char-regex": "^1.0.2", @@ -4627,12 +5005,14 @@ "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4642,6 +5022,7 @@ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4650,6 +5031,7 @@ "version": "2.15.2", "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.15.2.tgz", "integrity": "sha512-utmqVVS4HMDiwhIgI6Cr+KqA4aUhF3Sb755iO/qCiqxc5H9JW/9Z3N1RO/jKWpjP6q/Vx0lru7IYuiPvk+2/ng==", + "license": "Unlicense", "dependencies": { "@noble/ciphers": "^0.5.1", "@noble/curves": "1.2.0", @@ -4672,6 +5054,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "license": "MIT", "dependencies": { "@noble/hashes": "1.3.2" }, @@ -4683,6 +5066,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "license": "MIT", "engines": { "node": ">= 16" }, @@ -4694,6 +5078,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "license": "MIT", "engines": { "node": ">= 16" }, @@ -4710,17 +5095,20 @@ "type": "individual", "url": "https://paulmillr.com/funding/" } - ] + ], + "license": "MIT" }, "node_modules/nostr-wasm": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz", - "integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==" + "integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==", + "license": "MIT" }, "node_modules/nunjucks": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", + "license": "BSD-2-Clause", "dependencies": { "a-sync-waterfall": "^1.0.0", "asap": "^2.0.3", @@ -4745,6 +5133,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "license": "MIT", "engines": { "node": ">= 6" } @@ -4753,6 +5142,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4761,6 +5151,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", "engines": { "node": ">= 6" } @@ -4769,6 +5160,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } @@ -4778,6 +5170,7 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "deep-is": "^0.1.3", @@ -4796,6 +5189,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "yocto-queue": "^0.1.0" @@ -4812,6 +5206,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "p-limit": "^3.0.2" @@ -4827,6 +5222,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", "engines": { "node": ">=6" } @@ -4834,13 +5230,15 @@ "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "callsites": "^3.0.0" @@ -4853,6 +5251,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", "engines": { "node": ">=8" } @@ -4861,6 +5260,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", "engines": { "node": ">=8" } @@ -4868,12 +5268,14 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -4889,13 +5291,15 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pathval": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14.16" } @@ -4903,13 +5307,15 @@ "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -4921,6 +5327,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4929,6 +5336,7 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", "engines": { "node": ">= 6" } @@ -4936,15 +5344,17 @@ "node_modules/plantuml-encoder": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/plantuml-encoder/-/plantuml-encoder-1.4.0.tgz", - "integrity": "sha512-sxMwpDw/ySY1WB2CE3+IdMuEcWibJ72DDOsXLkSmEaSzwEUaYBT6DWgOfBiHGCux4q433X6+OEFWjlVqp7gL6g==" + "integrity": "sha512-sxMwpDw/ySY1WB2CE3+IdMuEcWibJ72DDOsXLkSmEaSzwEUaYBT6DWgOfBiHGCux4q433X6+OEFWjlVqp7gL6g==", + "license": "MIT" }, "node_modules/playwright": { - "version": "1.54.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz", - "integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==", + "version": "1.54.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.2.tgz", + "integrity": "sha512-Hu/BMoA1NAdRUuulyvQC0pEqZ4vQbGfn8f7wPXcnqQmM+zct9UliKxsIkLNmz/ku7LElUNqmaiv1TG/aL5ACsw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.54.1" + "playwright-core": "1.54.2" }, "bin": { "playwright": "cli.js" @@ -4957,10 +5367,11 @@ } }, "node_modules/playwright-core": { - "version": "1.54.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz", - "integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==", + "version": "1.54.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.2.tgz", + "integrity": "sha512-n5r4HFbMmWsB4twG7tJLDN9gmBUeSPcsBZiWSE4DnYz9mJMAFqr2ID7+eGC9kpEnxExJ1epttwR59LEWCk8mtA==", "dev": true, + "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, @@ -4972,6 +5383,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", "engines": { "node": ">=10.13.0" } @@ -4994,6 +5406,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5007,6 +5420,7 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", @@ -5023,6 +5437,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "license": "MIT", "dependencies": { "camelcase-css": "^2.0.1" }, @@ -5052,6 +5467,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "lilconfig": "^3.1.1" }, @@ -5093,6 +5509,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.1.1" }, @@ -5107,6 +5524,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -5134,6 +5552,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "engines": { "node": ">=18.0" }, @@ -5160,6 +5579,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "engines": { "node": ">=12.0" }, @@ -5171,6 +5591,7 @@ "version": "6.0.10", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -5182,13 +5603,15 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">= 0.8.0" @@ -5199,6 +5622,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -5214,6 +5638,7 @@ "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.4.0.tgz", "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", "dev": true, + "license": "MIT", "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" @@ -5223,6 +5648,7 @@ "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "license": "MIT", "dependencies": { "asap": "~2.0.3" } @@ -5231,6 +5657,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.3.tgz", "integrity": "sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==", + "license": "MIT", "dependencies": { "pug-code-gen": "^3.0.3", "pug-filters": "^4.0.0", @@ -5246,6 +5673,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "license": "MIT", "dependencies": { "constantinople": "^4.0.1", "js-stringify": "^1.0.2", @@ -5256,6 +5684,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.3.tgz", "integrity": "sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==", + "license": "MIT", "dependencies": { "constantinople": "^4.0.1", "doctypes": "^1.1.0", @@ -5270,12 +5699,14 @@ "node_modules/pug-error": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz", - "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==" + "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", + "license": "MIT" }, "node_modules/pug-filters": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "license": "MIT", "dependencies": { "constantinople": "^4.0.1", "jstransformer": "1.0.0", @@ -5288,6 +5719,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "license": "MIT", "dependencies": { "character-parser": "^2.2.0", "is-expression": "^4.0.0", @@ -5298,6 +5730,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "license": "MIT", "dependencies": { "pug-error": "^2.0.0", "pug-walk": "^2.0.0" @@ -5307,6 +5740,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "license": "MIT", "dependencies": { "object-assign": "^4.1.1", "pug-walk": "^2.0.0" @@ -5316,6 +5750,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "license": "MIT", "dependencies": { "pug-error": "^2.0.0", "token-stream": "1.0.0" @@ -5324,12 +5759,14 @@ "node_modules/pug-runtime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", - "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==" + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", + "license": "MIT" }, "node_modules/pug-strip-comments": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "license": "MIT", "dependencies": { "pug-error": "^2.0.0" } @@ -5337,13 +5774,15 @@ "node_modules/pug-walk": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", - "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==" + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", + "license": "MIT" }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=6" @@ -5353,6 +5792,7 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", @@ -5369,6 +5809,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -5379,6 +5820,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -5391,6 +5833,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -5402,6 +5845,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -5416,6 +5860,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -5427,6 +5872,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -5439,12 +5885,14 @@ "node_modules/qrcode/node_modules/y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" }, "node_modules/qrcode/node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -5466,6 +5914,7 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -5491,42 +5940,37 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", "dependencies": { "pify": "^2.3.0" } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">= 14.18.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5534,12 +5978,14 @@ "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", @@ -5560,6 +6006,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=4" @@ -5569,6 +6016,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -5577,13 +6025,15 @@ "node_modules/robust-predicates": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" }, "node_modules/rollup": { - "version": "4.45.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.3.tgz", - "integrity": "sha512-STwyHZF3G+CrmZhB+qDiROq9s8B5PrOCYN6dtmOvwz585XBnyeHk1GTEhHJtUVb355/9uZhOazyVclTt5uahzA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "1.0.8" }, @@ -5595,26 +6045,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.45.3", - "@rollup/rollup-android-arm64": "4.45.3", - "@rollup/rollup-darwin-arm64": "4.45.3", - "@rollup/rollup-darwin-x64": "4.45.3", - "@rollup/rollup-freebsd-arm64": "4.45.3", - "@rollup/rollup-freebsd-x64": "4.45.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.45.3", - "@rollup/rollup-linux-arm-musleabihf": "4.45.3", - "@rollup/rollup-linux-arm64-gnu": "4.45.3", - "@rollup/rollup-linux-arm64-musl": "4.45.3", - "@rollup/rollup-linux-loongarch64-gnu": "4.45.3", - "@rollup/rollup-linux-ppc64-gnu": "4.45.3", - "@rollup/rollup-linux-riscv64-gnu": "4.45.3", - "@rollup/rollup-linux-riscv64-musl": "4.45.3", - "@rollup/rollup-linux-s390x-gnu": "4.45.3", - "@rollup/rollup-linux-x64-gnu": "4.45.3", - "@rollup/rollup-linux-x64-musl": "4.45.3", - "@rollup/rollup-win32-arm64-msvc": "4.45.3", - "@rollup/rollup-win32-ia32-msvc": "4.45.3", - "@rollup/rollup-win32-x64-msvc": "4.45.3", + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", "fsevents": "~2.3.2" } }, @@ -5636,6 +6086,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -5643,13 +6094,15 @@ "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", "dev": true, + "license": "MIT", "dependencies": { "mri": "^1.1.0" }, @@ -5660,13 +6113,15 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -5677,18 +6132,21 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" }, "node_modules/set-cookie-parser": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -5700,6 +6158,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", "engines": { "node": ">=8" } @@ -5708,12 +6167,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", "engines": { "node": ">=14" }, @@ -5726,6 +6187,7 @@ "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", "dev": true, + "license": "MIT", "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", @@ -5739,6 +6201,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "license": "MIT", "dependencies": { "unicode-emoji-modifier-base": "^1.0.0" }, @@ -5750,6 +6213,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -5758,6 +6222,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -5766,18 +6231,21 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/std-env": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5792,6 +6260,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5805,6 +6274,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5817,6 +6287,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5829,6 +6300,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=8" @@ -5842,6 +6314,7 @@ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", "dev": true, + "license": "MIT", "dependencies": { "js-tokens": "^9.0.1" }, @@ -5853,6 +6326,7 @@ "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", @@ -5874,6 +6348,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -5882,6 +6357,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", "engines": { "node": ">= 6" } @@ -5890,6 +6366,7 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -5909,6 +6386,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -5923,6 +6401,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -5934,6 +6413,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5942,10 +6422,11 @@ } }, "node_modules/svelte": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.37.0.tgz", - "integrity": "sha512-BAHgWdKncZ4F1DVBrkKAvelx2Nv3mR032ca8/yj9Gxf5s9zzK1uGXiZTjCFDvmO2e9KQfcR2lEkVjw+ZxExJow==", + "version": "5.37.3", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.37.3.tgz", + "integrity": "sha512-7t/ejshehHd+95z3Z7ebS7wsqHDQxi/8nBTuTRwpMgNegfRBfuitCSKTUDKIBOExqfT2+DhQ2VLG8Xn+cBXoaQ==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -5971,6 +6452,7 @@ "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.0.tgz", "integrity": "sha512-Iz8dFXzBNAM7XlEIsUjUGQhbEE+Pvv9odb9+0+ITTgFWZBGeJRRYqHUUglwe2EkLD5LIsQaAc4IUJyvtKuOO5w==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", @@ -5989,39 +6471,12 @@ "typescript": ">=5.0.0" } }, - "node_modules/svelte-check/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/svelte-check/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/svelte-eslint-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.3.0.tgz", - "integrity": "sha512-VCgMHKV7UtOGcGLGNFSbmdm6kEKjtzo5nnpGU/mnx4OsFY6bZ7QwRF5DUx+Hokw5Lvdyo8dpk8B1m8mliomrNg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.3.1.tgz", + "integrity": "sha512-0Iztj5vcOVOVkhy1pbo5uA9r+d3yaVoE5XPc9eABIWDOSJZ2mOsZ4D+t45rphWCOr0uMw3jtSG2fh2e7GvKnPg==", "dev": true, + "license": "MIT", "dependencies": { "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.0.0", @@ -6050,6 +6505,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -6063,6 +6519,7 @@ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.6" } @@ -6072,6 +6529,7 @@ "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", "dev": true, + "license": "MIT", "dependencies": { "svg.js": "^2.0.1" }, @@ -6084,6 +6542,7 @@ "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", "dev": true, + "license": "MIT", "dependencies": { "svg.js": ">=2.3.x" }, @@ -6096,6 +6555,7 @@ "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", "dev": true, + "license": "MIT", "dependencies": { "svg.js": "^2.2.5" }, @@ -6107,13 +6567,15 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/svg.pathmorphing.js": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", "dev": true, + "license": "MIT", "dependencies": { "svg.js": "^2.4.0" }, @@ -6126,6 +6588,7 @@ "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", "dev": true, + "license": "MIT", "dependencies": { "svg.js": "^2.6.5", "svg.select.js": "^2.1.2" @@ -6139,6 +6602,7 @@ "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", "dev": true, + "license": "MIT", "dependencies": { "svg.js": "^2.2.5" }, @@ -6151,6 +6615,7 @@ "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", "dev": true, + "license": "MIT", "dependencies": { "svg.js": "^2.6.5" }, @@ -6163,6 +6628,7 @@ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", "dev": true, + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" @@ -6172,6 +6638,7 @@ "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -6204,6 +6671,54 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tailwindcss/node_modules/postcss-load-config": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", @@ -6218,6 +6733,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "lilconfig": "^3.0.0", "yaml": "^2.3.4" @@ -6242,6 +6758,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -6250,10 +6767,35 @@ "node": ">=4" } }, + "node_modules/tailwindcss/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/tailwindcss/node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", "dependencies": { "any-promise": "^1.0.0" } @@ -6262,6 +6804,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -6273,19 +6816,22 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", "dev": true, + "license": "MIT", "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" @@ -6302,6 +6848,7 @@ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "dev": true, + "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" } @@ -6311,6 +6858,7 @@ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -6320,6 +6868,7 @@ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -6328,6 +6877,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -6338,13 +6888,15 @@ "node_modules/token-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", - "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==" + "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", + "license": "MIT" }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -6352,24 +6904,28 @@ "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" }, "node_modules/tseep": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/tseep/-/tseep-1.3.1.tgz", - "integrity": "sha512-ZPtfk1tQnZVyr7BPtbJ93qaAh2lZuIOpTMjhrYa4XctT8xe7t4SAW9LIxrySDuYMsfNNayE51E/WNGrNVgVicQ==" + "integrity": "sha512-ZPtfk1tQnZVyr7BPtbJ93qaAh2lZuIOpTMjhrYa4XctT8xe7t4SAW9LIxrySDuYMsfNNayE51E/WNGrNVgVicQ==", + "license": "MIT" }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true + "dev": true, + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "prelude-ls": "^1.2.1" @@ -6379,10 +6935,11 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "devOptional": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6394,12 +6951,14 @@ "node_modules/typescript-lru-cache": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/typescript-lru-cache/-/typescript-lru-cache-2.0.0.tgz", - "integrity": "sha512-Jp57Qyy8wXeMkdNuZiglE6v2Cypg13eDA1chHwDG6kq51X7gk4K7P7HaDdzZKCxkegXkVHNcPD0n5aW6OZH3aA==" + "integrity": "sha512-Jp57Qyy8wXeMkdNuZiglE6v2Cypg13eDA1chHwDG6kq51X7gk4K7P7HaDdzZKCxkegXkVHNcPD0n5aW6OZH3aA==", + "license": "MIT" }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" @@ -6412,12 +6971,14 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/unicode-emoji-modifier-base": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "license": "MIT", "engines": { "node": ">=4" } @@ -6426,6 +6987,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/unxhr/-/unxhr-1.2.0.tgz", "integrity": "sha512-6cGpm8NFXPD9QbSNx0cD2giy7teZ6xOkCUH3U89WKVkL9N9rBrWjlCwhR94Re18ZlAop4MOc3WU1M3Hv/bgpIw==", + "license": "MIT", "engines": { "node": ">=8.11" } @@ -6449,6 +7011,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -6465,6 +7028,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "peer": true, "dependencies": { "punycode": "^2.1.0" @@ -6473,13 +7037,15 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/vite": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -6554,6 +7120,7 @@ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", "dev": true, + "license": "MIT", "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", @@ -6577,6 +7144,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -6590,6 +7158,12 @@ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, @@ -6604,6 +7178,7 @@ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, + "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -6675,6 +7250,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6683,6 +7259,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -6696,13 +7273,15 @@ "node_modules/which-module": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, + "license": "MIT", "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" @@ -6718,6 +7297,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "license": "MIT", "dependencies": { "@babel/parser": "^7.9.6", "@babel/types": "^7.9.6", @@ -6733,6 +7313,7 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=0.10.0" @@ -6741,12 +7322,14 @@ "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -6764,6 +7347,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -6779,31 +7363,33 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "bin": { - "yaml": "bin.mjs" - }, + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", "engines": { - "node": ">= 14.6" + "node": ">= 6" } }, "node_modules/yargs": { "version": "17.3.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", + "license": "MIT", "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -6821,6 +7407,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", "engines": { "node": ">=12" } @@ -6830,6 +7417,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=10" @@ -6842,7 +7430,8 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", - "dev": true + "dev": true, + "license": "MIT" } } } diff --git a/playwright.config.ts b/playwright.config.ts index 4ef00bd..5779001 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -27,7 +27,7 @@ export default defineConfig({ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://127.0.0.1:3000', + baseURL: 'http://localhost:5173', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", @@ -72,11 +72,11 @@ export default defineConfig({ ], /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://127.0.0.1:3000', - // reuseExistingServer: !process.env.CI, - // }, + webServer: { + command: 'npm run dev', + url: 'http://localhost:5173', + reuseExistingServer: !process.env.CI, + }, // Glob patterns or regular expressions to ignore test files. // testIgnore: '*test-assets', diff --git a/src/app.css b/src/app.css index 7a55d9d..b5169ae 100644 --- a/src/app.css +++ b/src/app.css @@ -201,6 +201,20 @@ .network-node-content { @apply fill-primary-100; } + + /* Person link colors */ + .person-link-signed { + @apply stroke-green-500; + } + + .person-link-referenced { + @apply stroke-blue-400; + } + + /* Person anchor node */ + .person-anchor-node { + @apply fill-green-400 stroke-green-600; + } } /* Utilities can be applied via the @apply directive. */ @@ -233,6 +247,28 @@ @apply text-base font-semibold; } + /* Line clamp utilities for text truncation */ + .line-clamp-1 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + } + + .line-clamp-2 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + } + + .line-clamp-3 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; + } + /* Lists */ .ol-leather li a, .ul-leather li a { diff --git a/src/lib/components/EventKindFilter.svelte b/src/lib/components/EventKindFilter.svelte new file mode 100644 index 0000000..5f7b992 --- /dev/null +++ b/src/lib/components/EventKindFilter.svelte @@ -0,0 +1,204 @@ + + +
+
+ {#each $visualizationConfig.eventConfigs as ec} + {@const isEnabled = ec.enabled !== false} + {@const isLoaded = (eventCounts[ec.kind] || 0) > 0} + {@const borderColor = isLoaded ? 'border-green-500' : 'border-red-500'} + + + + {/each} + + {#if !showAddInput} + + {/if} + + +
+ + {#if showAddInput} +
+ { + const value = (e.target as HTMLInputElement).value; + validateKind(value); + }} + /> + + +
+ {#if inputError} +

+ {inputError} +

+ {/if} + {/if} + +
+

+ + Green border = Events loaded +

+

+ + Red border = Not loaded (click Reload to fetch) +

+
+
+ + \ No newline at end of file diff --git a/src/lib/components/EventLimitControl.svelte b/src/lib/components/EventLimitControl.svelte deleted file mode 100644 index 9a32a56..0000000 --- a/src/lib/components/EventLimitControl.svelte +++ /dev/null @@ -1,52 +0,0 @@ - - -
- - - -
diff --git a/src/lib/components/EventTypeConfig.svelte b/src/lib/components/EventTypeConfig.svelte new file mode 100644 index 0000000..4d7bfc7 --- /dev/null +++ b/src/lib/components/EventTypeConfig.svelte @@ -0,0 +1,274 @@ + + +
+ + Showing {Object.values(eventCounts).reduce((a: any, b: any) => a + b, 0)} of {Object.values(eventCounts).reduce((a: any, b: any) => a + b, 0)} events + + + +
+ {#each $visualizationConfig.eventConfigs as config} + {@const isLoaded = (eventCounts[config.kind] || 0) > 0} + {@const isDisabled = config.enabled === false} + {@const color = getEventKindColor(config.kind)} + {@const borderColor = isLoaded ? 'border-green-500' : 'border-red-500'} +
+ + + + + + {#if config.kind === 0} + handleLimitChange(config.kind, e.currentTarget.value)} + title="Max profiles to display" + /> + + of {profileStats.totalFetched} fetched + + {:else} + + handleLimitChange(config.kind, e.currentTarget.value)} + title="Max to display" + disabled={(config.kind === 30041 || config.kind === 30818) && config.showAll} + /> + + + {#if config.kind === 30041 || config.kind === 30818} + + {/if} + {/if} + + + {#if config.kind === 30040} + Nested Levels: + handleNestedLevelsChange(e.currentTarget.value)} + title="Levels to traverse" + /> + {/if} + + + {#if config.kind === 3} + + {/if} + + + {#if config.kind !== 0 && isLoaded} + + ({eventCounts[config.kind]}) + + {:else if config.kind !== 0} + + (not loaded) + + {/if} +
+ {/each} +
+ + + {#if showAddInput} +
+ { + const validation = validateEventKind(e.currentTarget.value, existingKinds); + inputError = validation.error; + }} + /> + + +
+ {#if inputError} +

+ {inputError} +

+ {/if} + {:else} + + {/if} + + + + + +
+

+ + Green = Events loaded +

+

+ + Red = Not loaded (click Reload) +

+
+
\ No newline at end of file diff --git a/src/lib/components/Navigation.svelte b/src/lib/components/Navigation.svelte index fdcfe32..e155c03 100644 --- a/src/lib/components/Navigation.svelte +++ b/src/lib/components/Navigation.svelte @@ -31,6 +31,7 @@ Visualize Getting Started Events + My Notes About Contact diff --git a/src/lib/components/RelayStatus.svelte b/src/lib/components/RelayStatus.svelte index 949c000..fba24c3 100644 --- a/src/lib/components/RelayStatus.svelte +++ b/src/lib/components/RelayStatus.svelte @@ -136,7 +136,7 @@ import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
{#each relayStatuses as status} -
+
{status.url}
diff --git a/src/lib/components/publications/PublicationFeed.svelte b/src/lib/components/publications/PublicationFeed.svelte index 50c50de..48e4eba 100644 --- a/src/lib/components/publications/PublicationFeed.svelte +++ b/src/lib/components/publications/PublicationFeed.svelte @@ -7,10 +7,9 @@ import { onMount, onDestroy } from "svelte"; import { getMatchingTags, - NDKRelaySetFromNDK, - type NDKEvent, - type NDKRelaySet, } from "$lib/utils/nostrUtils"; + import { WebSocketPool } from "$lib/data_structures/websocket_pool"; + import { NDKEvent } from "@nostr-dev-kit/ndk"; import { searchCache } from "$lib/utils/searchCache"; import { indexEventCache } from "$lib/utils/indexEventCache"; import { isValidNip05Address } from "$lib/utils/search_utility"; @@ -139,21 +138,54 @@ async function fetchFromRelay(relay: string): Promise { try { console.debug(`[PublicationFeed] Fetching from relay: ${relay}`); - const relaySet = NDKRelaySetFromNDK.fromRelayUrls([relay], ndk); - let eventSet = await ndk - .fetchEvents( - { - kinds: [indexKind], - limit: 1000, // Increased limit to get more events - }, - { - groupable: false, - skipVerification: false, - skipValidation: false, - }, - relaySet, - ) - .withTimeout(5000); // Reduced timeout to 5 seconds for faster response + + // Use WebSocketPool to get a pooled connection + const ws = await WebSocketPool.instance.acquire(relay); + const subId = crypto.randomUUID(); + + // Create a promise that resolves with the events + const eventPromise = new Promise>((resolve, reject) => { + const events = new Set(); + + const messageHandler = (ev: MessageEvent) => { + try { + const data = JSON.parse(ev.data); + + if (data[0] === "EVENT" && data[1] === subId) { + const event = new NDKEvent(ndk, data[2]); + events.add(event); + } else if (data[0] === "EOSE" && data[1] === subId) { + resolve(events); + } + } catch (error) { + console.error(`[PublicationFeed] Error parsing message from ${relay}:`, error); + } + }; + + const errorHandler = (ev: Event) => { + reject(new Error(`WebSocket error for ${relay}: ${ev}`)); + }; + + ws.addEventListener("message", messageHandler); + ws.addEventListener("error", errorHandler); + + // Send the subscription request + ws.send(JSON.stringify([ + "REQ", + subId, + { kinds: [indexKind], limit: 1000 } + ])); + + // Set up cleanup + setTimeout(() => { + ws.removeEventListener("message", messageHandler); + ws.removeEventListener("error", errorHandler); + WebSocketPool.instance.release(ws); + resolve(events); + }, 5000); + }); + + let eventSet = await eventPromise; console.debug(`[PublicationFeed] Raw events from ${relay}:`, eventSet.size); eventSet = filterValidIndexEvents(eventSet); @@ -292,7 +324,7 @@ // Debounced search function const debouncedSearch = debounceAsync(async (query: string) => { console.debug("[PublicationFeed] Search query changed:", query); - if (query.trim()) { + if (query && query.trim()) { const filtered = filterEventsBySearch(allIndexEvents); eventsInView = filtered.slice(0, 30); endOfFeed = filtered.length <= 30; @@ -303,10 +335,6 @@ }, 300); $effect(() => { - console.debug( - "[PublicationFeed] Search query effect triggered:", - props.searchQuery, - ); debouncedSearch(props.searchQuery); }); @@ -368,7 +396,7 @@
{#if loading && eventsInView.length === 0} {#each getSkeletonIds() as id} diff --git a/src/lib/components/publications/PublicationHeader.svelte b/src/lib/components/publications/PublicationHeader.svelte index d0ed9b3..c1c6222 100644 --- a/src/lib/components/publications/PublicationHeader.svelte +++ b/src/lib/components/publications/PublicationHeader.svelte @@ -1,5 +1,5 @@ {#if title != null && href != null} - +
{#if image} -
-
- -
-

{title}

-

+
+ diff --git a/src/lib/components/publications/PublicationSection.svelte b/src/lib/components/publications/PublicationSection.svelte index 3793d85..6c5b6be 100644 --- a/src/lib/components/publications/PublicationSection.svelte +++ b/src/lib/components/publications/PublicationSection.svelte @@ -11,6 +11,7 @@ import { getMatchingTags } from "$lib/utils/nostrUtils"; import type { SveltePublicationTree } from "./svelte_publication_tree.svelte"; import { postProcessAdvancedAsciidoctorHtml } from "$lib/utils/markup/advancedAsciidoctorPostProcessor"; + import { parseAdvancedmarkup } from "$lib/utils/markup/advancedMarkupParser"; let { address, @@ -48,10 +49,19 @@ ); let leafContent: Promise = $derived.by(async () => { - const content = (await leafEvent)?.content ?? ""; - const converted = asciidoctor.convert(content); - const processed = await postProcessAdvancedAsciidoctorHtml(converted.toString()); - return processed; + const event = await leafEvent; + const content = event?.content ?? ""; + + // AI-NOTE: Kind 30023 events contain Markdown content, not AsciiDoc + // Use parseAdvancedmarkup for 30023 events, Asciidoctor for 30041/30818 events + if (event?.kind === 30023) { + return await parseAdvancedmarkup(content); + } else { + // For 30041 and 30818 events, use Asciidoctor (AsciiDoc) + const converted = asciidoctor.convert(content); + const processed = await postProcessAdvancedAsciidoctorHtml(converted.toString()); + return processed; + } }); let previousLeafEvent: NDKEvent | null = $derived.by(() => { diff --git a/src/lib/components/util/ArticleNav.svelte b/src/lib/components/util/ArticleNav.svelte index 1ab1655..7928b66 100644 --- a/src/lib/components/util/ArticleNav.svelte +++ b/src/lib/components/util/ArticleNav.svelte @@ -4,12 +4,14 @@ CaretLeftOutline, CloseOutline, GlobeOutline, + ChartOutline, } from "flowbite-svelte-icons"; import { Button } from "flowbite-svelte"; import { publicationColumnVisibility } from "$lib/stores"; import { userBadge } from "$lib/snippets/UserSnippets.svelte"; import type { NDKEvent } from "@nostr-dev-kit/ndk"; import { onDestroy, onMount } from "svelte"; + import { goto } from "$app/navigation"; let { publicationType, indexEvent } = $props<{ rootId: any; @@ -25,6 +27,7 @@ indexEvent.getMatchingTags("p")[0]?.[1] ?? null, ); let isLeaf: boolean = $derived(indexEvent.kind === 30041); + let isIndexEvent: boolean = $derived(indexEvent.kind === 30040); let lastScrollY = $state(0); let isVisible = $state(true); @@ -102,6 +105,11 @@ } } + function visualizePublication() { + const eventId = indexEvent.id; + goto(`/visualize?event=${eventId}`); + } + let unsubscribe: () => void; onMount(() => { window.addEventListener("scroll", handleScroll); @@ -133,7 +141,7 @@ {/if} - {#if !isLeaf} + {#if isIndexEvent} {#if publicationType === "blog"} {/if} +

diff --git a/src/lib/components/util/ContainingIndexes.svelte b/src/lib/components/util/ContainingIndexes.svelte index f2b57f4..be06f3e 100644 --- a/src/lib/components/util/ContainingIndexes.svelte +++ b/src/lib/components/util/ContainingIndexes.svelte @@ -47,12 +47,12 @@ function navigateToIndex(indexEvent: NDKEvent) { const dTag = getMatchingTags(indexEvent, "d")[0]?.[1]; if (dTag) { - goto(`/publication?d=${encodeURIComponent(dTag)}`); + goto(`/publication/d/${encodeURIComponent(dTag)}`); } else { // Fallback to naddr try { const naddr = naddrEncode(indexEvent, $activeInboxRelays); - goto(`/publication?id=${encodeURIComponent(naddr)}`); + goto(`/publication/naddr/${encodeURIComponent(naddr)}`); } catch (err) { console.error("[ContainingIndexes] Error creating naddr:", err); } diff --git a/src/lib/components/util/ViewPublicationLink.svelte b/src/lib/components/util/ViewPublicationLink.svelte index fd7538d..6217bce 100644 --- a/src/lib/components/util/ViewPublicationLink.svelte +++ b/src/lib/components/util/ViewPublicationLink.svelte @@ -21,52 +21,69 @@ return getEventType(event.kind || 0) === "addressable"; } - function getNaddrAddress(event: NDKEvent): string | null { - if (!isAddressableEvent(event)) { - return null; - } - try { - return naddrEncode(event, $activeInboxRelays); - } catch { - return null; - } - } - + // AI-NOTE: Always ensure the returned address is a valid naddr1... string. + // If the tag value is a raw coordinate (kind:pubkey:d-tag), encode it. + // If it's already naddr1..., use as-is. Otherwise, fallback to event's own naddr. function getViewPublicationNaddr(event: NDKEvent): string | null { // First, check for a-tags with 'defer' - these indicate the event is deferring to someone else's version const aTags = getMatchingTags(event, "a"); for (const tag of aTags) { if (tag.length >= 2 && tag.includes("defer")) { - // This is a deferral to someone else's addressable event - return tag[1]; // Return the addressable event address + const value = tag[1]; + if (value.startsWith("naddr1")) { + return value; + } + // Check for coordinate format: kind:pubkey:d-tag + const coordMatch = value.match(/^(\d+):([0-9a-fA-F]{64}):(.+)$/); + if (coordMatch) { + const [_, kind, pubkey, dTag] = coordMatch; + try { + return naddrEncode({ kind: Number(kind), pubkey, tags: [["d", dTag]] } as NDKEvent, $activeInboxRelays); + } catch { + return null; + } + } + // Fallback: if not naddr1 or coordinate, ignore } } // For deferred events with deferral tag, use the deferral naddr instead of the event's own naddr const deferralNaddr = getDeferralNaddr(event); if (deferralNaddr) { - return deferralNaddr; + if (deferralNaddr.startsWith("naddr1")) { + return deferralNaddr; + } + const coordMatch = deferralNaddr.match(/^(\d+):([0-9a-fA-F]{64}):(.+)$/); + if (coordMatch) { + const [_, kind, pubkey, dTag] = coordMatch; + try { + return naddrEncode({ kind: Number(kind), pubkey, tags: [["d", dTag]] } as NDKEvent, $activeInboxRelays); + } catch { + return null; + } + } } // Otherwise, use the event's own naddr if it's addressable return getNaddrAddress(event); } + function getNaddrAddress(event: NDKEvent): string | null { + if (!isAddressableEvent(event)) { + return null; + } + try { + return naddrEncode(event, $activeInboxRelays); + } catch { + return null; + } + } + function navigateToPublication() { const naddrAddress = getViewPublicationNaddr(event); - console.log("ViewPublicationLink: navigateToPublication called", { - eventKind: event.kind, - naddrAddress, - isAddressable: isAddressableEvent(event), - }); if (naddrAddress) { - console.log( - "ViewPublicationLink: Navigating to publication:", - naddrAddress, - ); - goto(`/publication?id=${encodeURIComponent(naddrAddress)}`); - } else { - console.log("ViewPublicationLink: No naddr address found for event"); + const url = `/publication/naddr/${naddrAddress}`; + goto(url); } } diff --git a/src/lib/consts.ts b/src/lib/consts.ts index ef41e0d..90afa53 100644 --- a/src/lib/consts.ts +++ b/src/lib/consts.ts @@ -2,7 +2,7 @@ export const wikiKind = 30818; export const indexKind = 30040; -export const zettelKinds = [30041, 30818]; +export const zettelKinds = [30041, 30818, 30023]; export const communityRelays = [ "wss://theforest.nostr1.com", @@ -29,18 +29,18 @@ export const secondaryRelays = [ export const anonymousRelays = [ "wss://freelay.sovbit.host", - "wss://thecitadel.nostr1.com" + "wss://thecitadel.nostr1.com", ]; export const lowbandwidthRelays = [ "wss://theforest.nostr1.com", "wss://thecitadel.nostr1.com", - "wss://aggr.nostr.land" + "wss://aggr.nostr.land", ]; export const localRelays: string[] = [ "wss://localhost:8080", - "wss://localhost:4869" + "wss://localhost:4869", ]; export enum FeedType { diff --git a/src/lib/data_structures/publication_tree.ts b/src/lib/data_structures/publication_tree.ts index 8b3a8f3..6871044 100644 --- a/src/lib/data_structures/publication_tree.ts +++ b/src/lib/data_structures/publication_tree.ts @@ -1,6 +1,7 @@ -import type NDK from "@nostr-dev-kit/ndk"; -import type { NDKEvent } from "@nostr-dev-kit/ndk"; import { Lazy } from "./lazy.ts"; +import type { NDKEvent } from "@nostr-dev-kit/ndk"; +import type NDK from "@nostr-dev-kit/ndk"; +import { fetchEventById } from "../utils/websocket_utils.ts"; enum PublicationTreeNodeType { Branch, @@ -583,6 +584,52 @@ export class PublicationTree implements AsyncIterable { .filter((tag) => tag[0] === "a") .map((tag) => tag[1]); + console.debug(`[PublicationTree] Current event ${currentEvent.id} has ${currentEvent.tags.length} tags:`, currentEvent.tags); + console.debug(`[PublicationTree] Found ${currentChildAddresses.length} a-tags in current event:`, currentChildAddresses); + + // If no a-tags found, try e-tags as fallback + if (currentChildAddresses.length === 0) { + const eTags = currentEvent.tags + .filter((tag) => tag[0] === "e" && tag[1] && /^[0-9a-fA-F]{64}$/.test(tag[1])); + + console.debug(`[PublicationTree] Found ${eTags.length} e-tags for current event ${currentEvent.id}:`, eTags.map(tag => tag[1])); + + // For e-tags with hex IDs, fetch the referenced events to get their addresses + const eTagPromises = eTags.map(async (tag) => { + try { + console.debug(`[PublicationTree] Fetching event for e-tag ${tag[1]} in depthFirstRetrieve`); + const referencedEvent = await fetchEventById(tag[1]); + + if (referencedEvent) { + // Construct the proper address format from the referenced event + const dTag = referencedEvent.tags.find(tag => tag[0] === "d")?.[1]; + if (dTag) { + const address = `${referencedEvent.kind}:${referencedEvent.pubkey}:${dTag}`; + console.debug(`[PublicationTree] Constructed address from e-tag in depthFirstRetrieve: ${address}`); + return address; + } else { + console.debug(`[PublicationTree] Referenced event ${tag[1]} has no d-tag in depthFirstRetrieve`); + } + } else { + console.debug(`[PublicationTree] Failed to fetch event for e-tag ${tag[1]} in depthFirstRetrieve - event not found`); + } + return null; + } catch (error) { + console.warn(`[PublicationTree] Failed to fetch event for e-tag ${tag[1]} in depthFirstRetrieve:`, error); + return null; + } + }); + + const resolvedAddresses = await Promise.all(eTagPromises); + const validAddresses = resolvedAddresses.filter(addr => addr !== null) as string[]; + + console.debug(`[PublicationTree] Resolved ${validAddresses.length} valid addresses from e-tags in depthFirstRetrieve:`, validAddresses); + + if (validAddresses.length > 0) { + currentChildAddresses.push(...validAddresses); + } + } + // If the current event has no children, it is a leaf. if (currentChildAddresses.length === 0) { // Return the first leaf if no address was provided. @@ -671,6 +718,52 @@ export class PublicationTree implements AsyncIterable { .filter((tag) => tag[0] === "a") .map((tag) => tag[1]); + console.debug(`[PublicationTree] Event ${event.id} has ${event.tags.length} tags:`, event.tags); + console.debug(`[PublicationTree] Found ${childAddresses.length} a-tags:`, childAddresses); + + // If no a-tags found, try e-tags as fallback + if (childAddresses.length === 0) { + const eTags = event.tags + .filter((tag) => tag[0] === "e" && tag[1] && /^[0-9a-fA-F]{64}$/.test(tag[1])); + + console.debug(`[PublicationTree] Found ${eTags.length} e-tags for event ${event.id}:`, eTags.map(tag => tag[1])); + + // For e-tags with hex IDs, fetch the referenced events to get their addresses + const eTagPromises = eTags.map(async (tag) => { + try { + console.debug(`[PublicationTree] Fetching event for e-tag ${tag[1]}`); + const referencedEvent = await fetchEventById(tag[1]); + + if (referencedEvent) { + // Construct the proper address format from the referenced event + const dTag = referencedEvent.tags.find(tag => tag[0] === "d")?.[1]; + if (dTag) { + const address = `${referencedEvent.kind}:${referencedEvent.pubkey}:${dTag}`; + console.debug(`[PublicationTree] Constructed address from e-tag: ${address}`); + return address; + } else { + console.debug(`[PublicationTree] Referenced event ${tag[1]} has no d-tag`); + } + } else { + console.debug(`[PublicationTree] Failed to fetch event for e-tag ${tag[1]}`); + } + return null; + } catch (error) { + console.warn(`[PublicationTree] Failed to fetch event for e-tag ${tag[1]}:`, error); + return null; + } + }); + + const resolvedAddresses = await Promise.all(eTagPromises); + const validAddresses = resolvedAddresses.filter(addr => addr !== null) as string[]; + + console.debug(`[PublicationTree] Resolved ${validAddresses.length} valid addresses from e-tags:`, validAddresses); + + if (validAddresses.length > 0) { + childAddresses.push(...validAddresses); + } + } + const node: PublicationTreeNode = { type: this.#getNodeType(event), status: PublicationTreeNodeStatus.Resolved, @@ -690,7 +783,10 @@ export class PublicationTree implements AsyncIterable { } #getNodeType(event: NDKEvent): PublicationTreeNodeType { - if (event.kind === 30040 && event.tags.some((tag) => tag[0] === "a")) { + if (event.kind === 30040 && ( + event.tags.some((tag) => tag[0] === "a") || + event.tags.some((tag) => tag[0] === "e" && tag[1] && /^[0-9a-fA-F]{64}$/.test(tag[1])) + )) { return PublicationTreeNodeType.Branch; } diff --git a/src/lib/navigator/EventNetwork/Legend.svelte b/src/lib/navigator/EventNetwork/Legend.svelte index b553cab..69866c0 100644 --- a/src/lib/navigator/EventNetwork/Legend.svelte +++ b/src/lib/navigator/EventNetwork/Legend.svelte @@ -1,14 +1,61 @@ - -
-
+ -
+
+ {#if expanded} -
    - -
  • -
    - - I - -
    - Index events (kind 30040) - Each with a unique pastel color + +
    +
  • +

    Node Types

    +
    + {#if nodeTypesExpanded} + + {:else} + + {/if} +
    + + + {#if nodeTypesExpanded} +
    +
      + + {#each Object.entries(eventCounts).sort(([a], [b]) => Number(a) - Number(b)) as [kindStr, count]} + {@const kind = Number(kindStr)} + {@const countNum = count as number} + {@const color = getEventKindColor(kind)} + {@const name = getEventKindName(kind)} + {#if countNum > 0} +
    • +
      + + +
      + + {kind} - {name} ({countNum}) + +
    • + {/if} + {/each} - -
    • -
      - - C - -
      - Content events (kinds 30041, 30818) - Publication sections +
    • + + + + + {#if starMode} + Radial connections from centers to related events + {:else} + Arrows indicate relationships and sequence + {/if} + +
    • + + + {#if showPersonNodes && personAnchors.length > 0} +
    • + + + + + Authored by person + +
    • +
    • + + + + + References person + +
    • + {/if} +
    +
    + {/if} +
+ + +
+ + + {#if tagControlsExpanded} +
+ +
+ + Show Tag Anchors +
+ + {#if showTagAnchors} + +
+ + + +
+ {/if} +
+ {/if} +
- -
  • - - - - Arrows indicate reading/sequence order -
  • - + + {#if showTags && tagAnchors.length > 0} +
    + + + {#if tagAnchorsExpanded} + {@const sortedAnchors = tagSortMode === 'count' + ? [...tagAnchors].sort((a, b) => b.count - a.count) + : [...tagAnchors].sort((a, b) => a.label.localeCompare(b.label)) + } +
    + {#if autoDisabledTags} +
    + Note: All {tagAnchors.length} tags were auto-disabled to prevent graph overload. Click individual tags below to enable them. +
    + {/if} + + +
    +
    + Sort by: + + +
    +
    + +
    + {#each sortedAnchors as tag} + {@const isDisabled = disabledTags.has(tag.value)} + + {/each} +
    +
    + {/if} +
    + {/if} + + +
    + + + {#if personVisualizerExpanded} +
    + +
    +
    + + Show Person Nodes +
    + + {#if showPersonNodes} +
    + + +
    + {/if} +
    + + {#if showPersonNodes && personAnchors.length > 0} +
    +

    + {#if totalPersonCount > displayedPersonCount} + Displaying {displayedPersonCount} of {totalPersonCount} people found: + {:else} + {personAnchors.length} people found: + {/if} +

    + + +
    + +
    + {#each personAnchors as person} + {@const isDisabled = disabledPersons.has(person.pubkey)} + + {/each} +
    + {/if} +
    + {/if} +
    +
    {/if}
    diff --git a/src/lib/navigator/EventNetwork/NodeTooltip.svelte b/src/lib/navigator/EventNetwork/NodeTooltip.svelte index ef455bf..5642a08 100644 --- a/src/lib/navigator/EventNetwork/NodeTooltip.svelte +++ b/src/lib/navigator/EventNetwork/NodeTooltip.svelte @@ -8,6 +8,12 @@ import type { NetworkNode } from "./types"; import { onMount } from "svelte"; import { getMatchingTags } from "$lib/utils/nostrUtils"; + import { getEventKindName } from "$lib/utils/eventColors"; + import { + getDisplayNameSync, + replacePubkeysWithDisplayNames, + } from "$lib/utils/profileCache"; + import {indexKind, zettelKinds, wikiKind} from "$lib/consts"; // Component props let { @@ -16,12 +22,14 @@ x, y, onclose, + starMode = false, } = $props<{ node: NetworkNode; // The node to display information for selected?: boolean; // Whether the node is selected (clicked) x: number; // X position for the tooltip y: number; // Y position for the tooltip onclose: () => void; // Function to call when closing the tooltip + starMode?: boolean; // Whether we're in star visualization mode }>(); // DOM reference and positioning @@ -32,6 +40,9 @@ // Maximum content length to display const MAX_CONTENT_LENGTH = 200; + // Publication event kinds (text/article based) + const PUBLICATION_KINDS = [wikiKind, indexKind, ...zettelKinds]; + /** * Gets the author name from the event tags */ @@ -39,7 +50,11 @@ if (node.event) { const authorTags = getMatchingTags(node.event, "author"); if (authorTags.length > 0) { - return authorTags[0][1]; + return getDisplayNameSync(authorTags[0][1]); + } + // Fallback to event pubkey + if (node.event.pubkey) { + return getDisplayNameSync(node.event.pubkey); } } return "Unknown"; @@ -71,6 +86,34 @@ return "View Publication"; } + /** + * Checks if this is a publication event + */ + function isPublicationEvent(kind: number): boolean { + return PUBLICATION_KINDS.includes(kind); + } + + /** + * Gets the appropriate URL for the event + */ + function getEventUrl(node: NetworkNode): string { + if (isPublicationEvent(node.kind)) { + return `/publication?id=${node.id}`; + } + return `/events?id=${node.id}`; + } + + /** + * Gets display text for the link + */ + function getLinkText(node: NetworkNode): string { + if (isPublicationEvent(node.kind)) { + return node.title || "Untitled Publication"; + } + // For arbitrary events, show event kind name + return node.title || `Event ${node.kind}`; + } + /** * Truncates content to a maximum length */ @@ -146,38 +189,91 @@ - + - - {#if node.isContainer && getSummaryTag(node)} -
    - Summary: - {truncateContent(getSummaryTag(node) || "")} + + {#if node.author} + + {:else} + + {/if} - - {#if node.content} -
    - {truncateContent(node.content)} -
    + {#if isPublicationEvent(node.kind)} + + {#if node.isContainer && getSummaryTag(node)} +
    + Summary: + {truncateContent(getSummaryTag(node) || "")} +
    + {/if} + + + {#if node.content} +
    + {truncateContent(node.content)} +
    + {/if} + {:else} + + {#if node.event?.content} +
    + Content: +
    {truncateContent(
    +              node.event.content,
    +            )}
    +
    + {/if} + + + {#if node.event?.tags && node.event.tags.length > 0} + + {/if} {/if} {#if selected} -
    Click node again to dismiss
    +
    + {#if isPublicationEvent(node.kind)} + Click to view publication · Click node again to dismiss + {:else} + Click to view event details · Click node again to dismiss + {/if} +
    {/if}
    diff --git a/src/lib/navigator/EventNetwork/Settings.svelte b/src/lib/navigator/EventNetwork/Settings.svelte index 2cff9e2..cd4e1e8 100644 --- a/src/lib/navigator/EventNetwork/Settings.svelte +++ b/src/lib/navigator/EventNetwork/Settings.svelte @@ -1,58 +1,147 @@ -
    -
    + -
    +
    + {#if expanded} -
    +
    - Showing {count} events from {$networkFetchLimit} headers + Showing {count} of {totalCount} events - - + + +
    + + {#if eventTypesExpanded} +
    + +
    + {/if} +
    + + +
    + + {#if visualSettingsExpanded} +
    +
    +
    + +

    + Toggle between star clusters (on) and linear sequence (off) + visualization +

    +
    +
    +
    + {/if} +
    {/if}
    diff --git a/src/lib/navigator/EventNetwork/TagTable.svelte b/src/lib/navigator/EventNetwork/TagTable.svelte new file mode 100644 index 0000000..55e603d --- /dev/null +++ b/src/lib/navigator/EventNetwork/TagTable.svelte @@ -0,0 +1,82 @@ + + + +{#if uniqueTags.length > 0} +
    +

    + {tagTypeLabels[selectedTagType] || 'Tags'} +

    + + + + + + + + + {#each uniqueTags as tag} + + + + + {/each} + +
    TagCount
    {tag.value}{tag.count}
    +
    +{:else} +
    + No {tagTypeLabels[selectedTagType]?.toLowerCase() || 'tags'} found +
    +{/if} + + \ No newline at end of file diff --git a/src/lib/navigator/EventNetwork/index.svelte b/src/lib/navigator/EventNetwork/index.svelte index ffbb44a..c9a8149 100644 --- a/src/lib/navigator/EventNetwork/index.svelte +++ b/src/lib/navigator/EventNetwork/index.svelte @@ -11,6 +11,16 @@ import type { NDKEvent } from "@nostr-dev-kit/ndk"; import { levelsToRender } from "$lib/state"; import { generateGraph, getEventColor } from "./utils/networkBuilder"; + import { getEventKindColor } from "$lib/utils/eventColors"; + import { + generateStarGraph, + applyStarLayout, + } from "./utils/starNetworkBuilder"; + import { + createStarSimulation, + applyInitialStarPositions, + createStarDragHandler, + } from "./utils/starForceSimulation"; import { createSimulation, setupDragHandlers, @@ -22,7 +32,20 @@ import NodeTooltip from "./NodeTooltip.svelte"; import type { NetworkNode, NetworkLink } from "./types"; import Settings from "./Settings.svelte"; + import { + enhanceGraphWithTags, + getTagAnchorColor, + } from "./utils/tagNetworkBuilder"; + import { + extractUniquePersons, + createPersonAnchorNodes, + createPersonLinks, + extractPersonAnchorInfo, + } from "./utils/personNetworkBuilder"; import { Button } from "flowbite-svelte"; + import { visualizationConfig } from "$lib/stores/visualizationConfig"; + import { get } from "svelte/store"; + import type { EventCounts } from "$lib/types"; // Type alias for D3 selections type Selection = any; @@ -45,9 +68,24 @@ } // Component props - let { events = [], onupdate } = $props<{ + let { + events = [], + followListEvents = [], + totalCount = 0, + onupdate, + onclear = () => {}, + onTagExpansionChange, + profileStats = { totalFetched: 0, displayLimit: 50 }, + allEventCounts = {} + } = $props<{ events?: NDKEvent[]; + followListEvents?: NDKEvent[]; + totalCount?: number; onupdate: () => void; + onclear?: () => void; + onTagExpansionChange?: (tags: string[]) => void; + profileStats?: { totalFetched: number; displayLimit: number }; + allEventCounts?: EventCounts; }>(); // Error state @@ -81,10 +119,57 @@ let svgGroup: Selection; let zoomBehavior: any; let svgElement: Selection; + + // Position cache to preserve node positions across updates + let nodePositions = new Map(); // Track current render level let currentLevels = $derived(levelsToRender); + // Star visualization state (default to true) + let starVisualization = $state(true); + + // Tag anchors state + let showTagAnchors = $state(false); + let selectedTagType = $state("t"); // Default to hashtags + let tagAnchorInfo = $state([]); + + // Store initial state to detect if component is being recreated + let componentId = Math.random(); + debug("Component created with ID:", componentId); + + // Event counts by kind - derived from events + let eventCounts = $derived.by(() => { + const counts: { [kind: number]: number } = {}; + events.forEach((event: NDKEvent) => { + if (event.kind !== undefined) { + counts[event.kind] = (counts[event.kind] || 0) + 1; + } + }); + return counts; + }); + + // Disabled tags state for interactive legend + let disabledTags = $state(new Set()); + + // Track if we've auto-disabled tags + let autoDisabledTags = $state(false); + + // Maximum number of tag anchors before auto-disabling + const MAX_TAG_ANCHORS = 20; + + // Person nodes state + let showPersonNodes = $state(false); + let personAnchorInfo = $state([]); + let disabledPersons = $state(new Set()); + let showSignedBy = $state(true); + let showReferenced = $state(true); + let personMap = $state>(new Map()); + let totalPersonCount = $state(0); + let displayedPersonCount = $state(0); + let hasInitializedPersons = $state(false); + + // Update dimensions when container changes $effect(() => { if (container) { @@ -144,193 +229,577 @@ .attr("stroke-width", 1); } + /** - * Updates the graph with new data - * Generates the graph from events, creates the simulation, and renders nodes and links + * Validates that required elements are available for graph rendering */ - function updateGraph() { - debug("Updating graph"); - errorMessage = null; + function validateGraphElements() { + if (!svg) { + throw new Error("SVG element not found"); + } - // Create variables to hold our selections - let link: any; - let node: any; - let dragHandler: any; - let nodes: NetworkNode[] = []; - let links: NetworkLink[] = []; + if (!events?.length) { + throw new Error("No events to render"); + } - try { - // Validate required elements - if (!svg) { - throw new Error("SVG element not found"); - } + if (!svgGroup) { + throw new Error("SVG group not found"); + } + } - if (!events?.length) { - throw new Error("No events to render"); - } + /** + * Generates graph data from events, including tag and person anchors + */ + function generateGraphData() { + debug("Generating graph with events", { + eventCount: events.length, + currentLevels, + starVisualization, + showTagAnchors, + }); - if (!svgGroup) { - throw new Error("SVG group not found"); - } + let graphData = starVisualization + ? generateStarGraph(events, Number(currentLevels)) + : generateGraph(events, Number(currentLevels)); - // Generate graph data from events - debug("Generating graph with events", { + // Enhance with tag anchors if enabled + if (showTagAnchors) { + debug("Enhancing graph with tags", { + selectedTagType, eventCount: events.length, - currentLevels, + width, + height }); + + // Get the display limit based on tag type + let displayLimit: number | undefined; + + graphData = enhanceGraphWithTags( + graphData, + events, + selectedTagType, + width, + height, + displayLimit, + ); + + // Extract tag anchor info for legend + const tagAnchors = graphData.nodes.filter((n) => n.isTagAnchor); + + debug("Tag anchors created", { + count: tagAnchors.length, + anchors: tagAnchors + }); + + tagAnchorInfo = tagAnchors.map((n) => ({ + type: n.tagType, + label: n.title, + count: n.connectedNodes?.length || 0, + color: getTagAnchorColor(n.tagType || ""), + })); + } else { + tagAnchorInfo = []; + } - const graphData = generateGraph(events, Number(currentLevels)); - nodes = graphData.nodes; - links = graphData.links; + // Add person nodes if enabled + if (showPersonNodes) { + debug("Creating person anchor nodes"); + + // Extract unique persons from events and follow lists + personMap = extractUniquePersons(events, followListEvents); + + // Create person anchor nodes based on filters + const personResult = createPersonAnchorNodes( + personMap, + width, + height, + showSignedBy, + showReferenced + ); + + const personAnchors = personResult.nodes; + totalPersonCount = personResult.totalCount; + displayedPersonCount = personAnchors.length; + + // Create links between person anchors and their events + const personLinks = createPersonLinks(personAnchors, graphData.nodes, personMap); + + // Add person anchors to the graph + graphData.nodes = [...graphData.nodes, ...personAnchors]; + graphData.links = [...graphData.links, ...personLinks]; + + // Extract person info for legend + personAnchorInfo = extractPersonAnchorInfo(personAnchors, personMap); + + // Auto-disable all person nodes by default (only on first time showing) + if (!hasInitializedPersons && personAnchors.length > 0) { + personAnchors.forEach(anchor => { + if (anchor.pubkey) { + disabledPersons.add(anchor.pubkey); + } + }); + hasInitializedPersons = true; + } + + debug("Person anchors created", { + count: personAnchors.length, + disabled: disabledPersons.size, + showSignedBy, + showReferenced + }); + } else { + personAnchorInfo = []; + // Reset initialization flag when person nodes are hidden + if (hasInitializedPersons && personAnchorInfo.length === 0) { + hasInitializedPersons = false; + disabledPersons.clear(); + } + } + + return graphData; + } - debug("Generated graph data", { - nodeCount: nodes.length, - linkCount: links.length, + /** + * Filters nodes and links based on disabled tags and persons + */ + function filterNodesAndLinks(graphData: { nodes: NetworkNode[]; links: NetworkLink[] }) { + let nodes = graphData.nodes; + let links = graphData.links; + + // Filter out disabled tag anchors and person nodes from nodes and links + if ((showTagAnchors && disabledTags.size > 0) || (showPersonNodes && disabledPersons.size > 0)) { + // Filter out disabled nodes + nodes = nodes.filter((node: NetworkNode) => { + if (node.isTagAnchor) { + const tagId = `${node.tagType}-${node.title}`; + return !disabledTags.has(tagId); + } + if (node.isPersonAnchor && node.pubkey) { + return !disabledPersons.has(node.pubkey); + } + return true; + }); + + // Filter out links to disabled nodes + links = links.filter((link: NetworkLink) => { + const source = link.source as NetworkNode; + const target = link.target as NetworkNode; + + // Check if either node is disabled + if (source.isTagAnchor) { + const tagId = `${source.tagType}-${source.title}`; + if (disabledTags.has(tagId)) return false; + } + if (target.isTagAnchor) { + const tagId = `${target.tagType}-${target.title}`; + if (disabledTags.has(tagId)) return false; + } + if (source.isPersonAnchor && source.pubkey) { + if (disabledPersons.has(source.pubkey)) return false; + } + if (target.isPersonAnchor && target.pubkey) { + if (disabledPersons.has(target.pubkey)) return false; + } + + return true; + }); + + debug("Filtered links for disabled tags", { + originalCount: graphData.links.length, + filteredCount: links.length, + disabledTags: Array.from(disabledTags) }); + } - if (!nodes.length) { - throw new Error("No nodes to render"); - } + return { nodes, links }; + } + + /** + * Saves current node positions to preserve them across updates + */ + function saveNodePositions(nodes: NetworkNode[]) { + if (simulation && nodes.length > 0) { + nodes.forEach(node => { + if (node.x != null && node.y != null) { + nodePositions.set(node.id, { + x: node.x, + y: node.y, + vx: node.vx, + vy: node.vy + }); + } + }); + debug("Saved positions for", nodePositions.size, "nodes"); + } + } - // Stop any existing simulation - if (simulation) { - debug("Stopping existing simulation"); - simulation.stop(); + /** + * Restores node positions from cache and initializes new nodes + */ + function restoreNodePositions(nodes: NetworkNode[]): number { + let restoredCount = 0; + nodes.forEach(node => { + const savedPos = nodePositions.get(node.id); + if (savedPos && !node.isTagAnchor) { // Don't restore tag anchor positions as they're fixed + node.x = savedPos.x; + node.y = savedPos.y; + node.vx = savedPos.vx || 0; + node.vy = savedPos.vy || 0; + restoredCount++; + } else if (!node.x && !node.y && !node.isTagAnchor && !node.isPersonAnchor) { + // Give disconnected nodes (like kind 0) random initial positions + node.x = width / 2 + (Math.random() - 0.5) * width * 0.5; + node.y = height / 2 + (Math.random() - 0.5) * height * 0.5; + node.vx = 0; + node.vy = 0; } + }); + return restoredCount; + } - // Create new simulation - debug("Creating new simulation"); - simulation = createSimulation(nodes, links, NODE_RADIUS, LINK_DISTANCE); + /** + * Sets up the D3 force simulation and drag handlers + */ + function setupSimulation(nodes: NetworkNode[], links: NetworkLink[], restoredCount: number) { + // Stop any existing simulation + if (simulation) { + debug("Stopping existing simulation"); + simulation.stop(); + } - // Center the nodes when the simulation is done - simulation.on("end", () => { - centerGraph(); - }); + // Create new simulation + debug("Creating new simulation"); + const hasRestoredPositions = restoredCount > 0; + let newSimulation: Simulation; + + if (starVisualization) { + // Use star-specific simulation + newSimulation = createStarSimulation(nodes, links, width, height); + // Apply initial star positioning only if we don't have restored positions + if (!hasRestoredPositions) { + applyInitialStarPositions(nodes, links, width, height); + } + } else { + // Use regular simulation + newSimulation = createSimulation(nodes, links, NODE_RADIUS, LINK_DISTANCE); + + // Add center force for disconnected nodes (like kind 0) + newSimulation.force("center", d3.forceCenter(width / 2, height / 2).strength(0.05)); + + // Add radial force to keep disconnected nodes in view + newSimulation.force("radial", d3.forceRadial(Math.min(width, height) / 3, width / 2, height / 2) + .strength((d: NetworkNode) => { + // Apply radial force only to nodes without links (disconnected nodes) + const hasLinks = links.some(l => + (l.source as NetworkNode).id === d.id || + (l.target as NetworkNode).id === d.id + ); + return hasLinks ? 0 : 0.1; + })); + } + + // Use gentler alpha for updates with restored positions + if (hasRestoredPositions) { + newSimulation.alpha(0.3); // Gentler restart + } - // Create drag handler - dragHandler = setupDragHandlers(simulation); - - // Update links - debug("Updating links"); - link = svgGroup - .selectAll("path.link") - .data(links, (d: NetworkLink) => `${d.source.id}-${d.target.id}`) - .join( - (enter: any) => - enter - .append("path") - .attr("class", "link network-link-leather") - .attr("stroke-width", 2) - .attr("marker-end", "url(#arrowhead)"), - (update: any) => update, - (exit: any) => exit.remove(), - ); + // Center the nodes when the simulation is done + newSimulation.on("end", () => { + if (!starVisualization) { + centerGraph(); + } + }); - // Update nodes - debug("Updating nodes"); - node = svgGroup - .selectAll("g.node") - .data(nodes, (d: NetworkNode) => d.id) - .join( - (enter: any) => { - const nodeEnter = enter - .append("g") - .attr("class", "node network-node-leather") - .call(dragHandler); - - // Larger transparent circle for better drag handling - nodeEnter - .append("circle") - .attr("class", "drag-circle") - .attr("r", NODE_RADIUS * 2.5) - .attr("fill", "transparent") - .attr("stroke", "transparent") - .style("cursor", "move"); - - // Visible circle - nodeEnter - .append("circle") - .attr("class", "visual-circle") - .attr("r", NODE_RADIUS) - .attr("stroke-width", 2); - - // Node label - nodeEnter - .append("text") - .attr("dy", "0.35em") - .attr("text-anchor", "middle") - .attr("fill", "black") - .attr("font-size", "12px"); - - return nodeEnter; - }, - (update: any) => update, - (exit: any) => exit.remove(), - ); + // Create drag handler + const dragHandler = starVisualization + ? createStarDragHandler(newSimulation) + : setupDragHandlers(newSimulation); - // Update node appearances - debug("Updating node appearances"); - node - .select("circle.visual-circle") - .attr("class", (d: NetworkNode) => - !d.isContainer - ? "visual-circle network-node-leather network-node-content" - : "visual-circle network-node-leather", - ) - .attr("fill", (d: NetworkNode) => - !d.isContainer - ? isDarkMode - ? CONTENT_COLOR_DARK - : CONTENT_COLOR_LIGHT - : getEventColor(d.id), - ); + return { simulation: newSimulation, dragHandler }; + } - node.select("text").text((d: NetworkNode) => (d.isContainer ? "I" : "C")); - - // Set up node interactions - debug("Setting up node interactions"); - node - .on("mouseover", (event: any, d: NetworkNode) => { - if (!selectedNodeId) { - tooltipVisible = true; - tooltipNode = d; - tooltipX = event.pageX; - tooltipY = event.pageY; - } - }) - .on("mousemove", (event: any) => { - if (!selectedNodeId) { - tooltipX = event.pageX; - tooltipY = event.pageY; - } - }) - .on("mouseout", () => { - if (!selectedNodeId) { - tooltipVisible = false; - tooltipNode = null; + /** + * Renders links in the SVG + */ + function renderLinks(links: NetworkLink[]) { + debug("Updating links"); + return svgGroup + .selectAll("path.link") + .data(links, (d: NetworkLink) => `${d.source.id}-${d.target.id}`) + .join( + (enter: any) => + enter + .append("path") + .attr("class", (d: any) => { + let classes = "link network-link-leather"; + if (d.connectionType === "signed-by") { + classes += " person-link-signed"; + } else if (d.connectionType === "referenced") { + classes += " person-link-referenced"; + } + return classes; + }) + .attr("stroke-width", 2) + .attr("marker-end", "url(#arrowhead)"), + (update: any) => update.attr("class", (d: any) => { + let classes = "link network-link-leather"; + if (d.connectionType === "signed-by") { + classes += " person-link-signed"; + } else if (d.connectionType === "referenced") { + classes += " person-link-referenced"; } - }) - .on("click", (event: any, d: NetworkNode) => { - event.stopPropagation(); - if (selectedNodeId === d.id) { - // Clicking the selected node again deselects it - selectedNodeId = null; - tooltipVisible = false; - } else { - // Select the node and show its tooltip - selectedNodeId = d.id; - tooltipVisible = true; - tooltipNode = d; - tooltipX = event.pageX; - tooltipY = event.pageY; + return classes; + }), + (exit: any) => exit.remove(), + ); + } + + /** + * Creates the node group and attaches drag handlers + */ + function createNodeGroup(enter: any, dragHandler: any) { + const nodeEnter = enter + .append('g') + .attr('class', 'node network-node-leather') + .call(dragHandler); + + // Larger transparent circle for better drag handling + nodeEnter + .append('circle') + .attr('class', 'drag-circle') + .attr('r', NODE_RADIUS * 2.5) + .attr('fill', 'transparent') + .attr('stroke', 'transparent') + .style('cursor', 'move'); + + // Add shape based on node type + nodeEnter.each(function (this: SVGGElement, d: NetworkNode) { + const g = d3.select(this); + if (d.isPersonAnchor) { + // Diamond shape for person anchors + g.append('rect') + .attr('class', 'visual-shape visual-diamond') + .attr('width', NODE_RADIUS * 1.5) + .attr('height', NODE_RADIUS * 1.5) + .attr('x', -NODE_RADIUS * 0.75) + .attr('y', -NODE_RADIUS * 0.75) + .attr('transform', 'rotate(45)') + .attr('stroke-width', 2); + } else { + // Circle for other nodes + g.append('circle') + .attr('class', 'visual-shape visual-circle') + .attr('r', NODE_RADIUS) + .attr('stroke-width', 2); + } + }); + + // Node label + nodeEnter + .append('text') + .attr('dy', '0.35em') + .attr('text-anchor', 'middle') + .attr('fill', 'black') + .attr('font-size', '12px') + .attr('stroke', 'none') + .attr('font-weight', 'bold') + .style('pointer-events', 'none'); + + return nodeEnter; + } + + /** + * Updates visual properties for all nodes + */ + function updateNodeAppearance(node: any) { + node + .select('.visual-shape') + .attr('class', (d: NetworkNode) => { + const shapeClass = d.isPersonAnchor ? 'visual-diamond' : 'visual-circle'; + const baseClasses = `visual-shape ${shapeClass} network-node-leather`; + if (d.isPersonAnchor) { + return `${baseClasses} person-anchor-node`; + } + if (d.isTagAnchor) { + return `${baseClasses} tag-anchor-node`; + } + if (!d.isContainer) { + return `${baseClasses} network-node-content`; + } + if (starVisualization && d.kind === 30040) { + return `${baseClasses} star-center-node`; + } + return baseClasses; + }) + .style('fill', (d: NetworkNode) => { + if (d.isPersonAnchor) { + if (d.isFromFollowList) { + return getEventKindColor(3); } - }); + return '#10B981'; + } + if (d.isTagAnchor) { + return getTagAnchorColor(d.tagType || ''); + } + const color = getEventKindColor(d.kind); + return color; + }) + .attr('opacity', 1) + .attr('r', (d: NetworkNode) => { + if (d.isPersonAnchor) return null; + if (d.isTagAnchor) { + return NODE_RADIUS * 0.75; + } + if (starVisualization && d.isContainer && d.kind === 30040) { + return NODE_RADIUS * 1.5; + } + return NODE_RADIUS; + }) + .attr('width', (d: NetworkNode) => { + if (!d.isPersonAnchor) return null; + return NODE_RADIUS * 1.5; + }) + .attr('height', (d: NetworkNode) => { + if (!d.isPersonAnchor) return null; + return NODE_RADIUS * 1.5; + }) + .attr('x', (d: NetworkNode) => { + if (!d.isPersonAnchor) return null; + return -NODE_RADIUS * 0.75; + }) + .attr('y', (d: NetworkNode) => { + if (!d.isPersonAnchor) return null; + return -NODE_RADIUS * 0.75; + }) + .attr('stroke-width', (d: NetworkNode) => { + if (d.isPersonAnchor) { + return 3; + } + if (d.isTagAnchor) { + return 3; + } + return 2; + }); + } + + /** + * Updates the text label for all nodes + */ + function updateNodeLabels(node: any) { + node + .select('text') + .text((d: NetworkNode) => { + if (d.isTagAnchor) { + return d.tagType === 't' ? '#' : 'T'; + } + return ''; + }) + .attr('font-size', (d: NetworkNode) => { + if (d.isTagAnchor) { + return '10px'; + } + if (starVisualization && d.isContainer && d.kind === 30040) { + return '14px'; + } + return '12px'; + }) + .attr('fill', (d: NetworkNode) => { + if (d.isTagAnchor) { + return 'white'; + } + return 'black'; + }) + .style('fill', (d: NetworkNode) => { + if (d.isTagAnchor) { + return 'white'; + } + return null; + }) + .attr('stroke', 'none') + .style('stroke', 'none'); + } - // Set up simulation tick handler - debug("Setting up simulation tick handler"); - if (simulation) { - simulation.on("tick", () => { - // Apply custom forces to each node + /** + * Renders nodes in the SVG (refactored for clarity) + */ + function renderNodes(nodes: NetworkNode[], dragHandler: any) { + debug('Updating nodes'); + const node = svgGroup + .selectAll('g.node') + .data(nodes, (d: NetworkNode) => d.id) + .join( + (enter: any) => createNodeGroup(enter, dragHandler), + (update: any) => { + update.call(dragHandler); + return update; + }, + (exit: any) => exit.remove(), + ); + + updateNodeAppearance(node); + updateNodeLabels(node); + + return node; + } + + /** + * Sets up mouse interactions for nodes (hover and click) + */ + function setupNodeInteractions(node: any) { + debug("Setting up node interactions"); + node + .on("mouseover", (event: any, d: NetworkNode) => { + if (!selectedNodeId) { + tooltipVisible = true; + tooltipNode = d; + tooltipX = event.pageX; + tooltipY = event.pageY; + } + }) + .on("mousemove", (event: any) => { + if (!selectedNodeId) { + tooltipX = event.pageX; + tooltipY = event.pageY; + } + }) + .on("mouseout", () => { + if (!selectedNodeId) { + tooltipVisible = false; + tooltipNode = null; + } + }) + .on("click", (event: any, d: NetworkNode) => { + event.stopPropagation(); + if (selectedNodeId === d.id) { + // Clicking the selected node again deselects it + selectedNodeId = null; + tooltipVisible = false; + } else { + // Select the node and show its tooltip + selectedNodeId = d.id; + tooltipVisible = true; + tooltipNode = d; + tooltipX = event.pageX; + tooltipY = event.pageY; + } + }); + } + + /** + * Sets up the simulation tick handler for animation + */ + function setupSimulationTickHandler( + simulation: Simulation | null, + nodes: NetworkNode[], + links: NetworkLink[], + link: any, + node: any + ) { + debug("Setting up simulation tick handler"); + if (simulation) { + simulation.on("tick", () => { + // Apply custom forces to each node + if (!starVisualization) { nodes.forEach((node) => { // Pull nodes toward the center applyGlobalLogGravity( @@ -342,36 +811,95 @@ // Pull connected nodes toward each other applyConnectedGravity(node, links, simulation!.alpha()); }); + } - // Update link positions - link.attr("d", (d: NetworkLink) => { - // Calculate angle between source and target - const dx = d.target.x! - d.source.x!; - const dy = d.target.y! - d.source.y!; - const angle = Math.atan2(dy, dx); + // Update link positions + link.attr("d", (d: NetworkLink) => { + // Calculate angle between source and target + const dx = d.target.x! - d.source.x!; + const dy = d.target.y! - d.source.y!; + const angle = Math.atan2(dy, dx); + + // Calculate start and end points with offsets for node radius + const sourceRadius = + starVisualization && + d.source.isContainer && + d.source.kind === 30040 + ? NODE_RADIUS * 1.5 + : NODE_RADIUS; + const targetRadius = + starVisualization && + d.target.isContainer && + d.target.kind === 30040 + ? NODE_RADIUS * 1.5 + : NODE_RADIUS; + + const sourceGap = sourceRadius; + const targetGap = targetRadius + ARROW_DISTANCE; + + const startX = d.source.x! + sourceGap * Math.cos(angle); + const startY = d.source.y! + sourceGap * Math.sin(angle); + const endX = d.target.x! - targetGap * Math.cos(angle); + const endY = d.target.y! - targetGap * Math.sin(angle); + + return `M${startX},${startY}L${endX},${endY}`; + }); - // Calculate start and end points with offsets for node radius - const sourceGap = NODE_RADIUS; - const targetGap = NODE_RADIUS + ARROW_DISTANCE; + // Update node positions + node.attr( + "transform", + (d: NetworkNode) => `translate(${d.x},${d.y})`, + ); + }); + } + } - const startX = d.source.x! + sourceGap * Math.cos(angle); - const startY = d.source.y! + sourceGap * Math.sin(angle); - const endX = d.target.x! - targetGap * Math.cos(angle); - const endY = d.target.y! - targetGap * Math.sin(angle); + /** + * Handles errors that occur during graph updates + */ + function handleGraphError(error: unknown) { + console.error("Error in updateGraph:", error); + errorMessage = `Error updating graph: ${error instanceof Error ? error.message : String(error)}`; + } - return `M${startX},${startY}L${endX},${endY}`; - }); + /** + * Updates the graph with new data + * Generates the graph from events, creates the simulation, and renders nodes and links + */ + function updateGraph() { + debug("updateGraph called", { + eventCount: events?.length, + starVisualization, + showTagAnchors, + selectedTagType, + disabledTagsCount: disabledTags.size + }); + errorMessage = null; - // Update node positions - node.attr( - "transform", - (d: NetworkNode) => `translate(${d.x},${d.y})`, - ); - }); + try { + validateGraphElements(); + const graphData = generateGraphData(); + + // Save current positions before filtering + saveNodePositions(graphData.nodes); + + const { nodes, links } = filterNodesAndLinks(graphData); + const restoredCount = restoreNodePositions(nodes); + + if (!nodes.length) { + throw new Error("No nodes to render"); } + + const { simulation: newSimulation, dragHandler } = setupSimulation(nodes, links, restoredCount); + simulation = newSimulation; + + const link = renderLinks(links); + const node = renderNodes(nodes, dragHandler); + + setupNodeInteractions(node); + setupSimulationTickHandler(simulation, nodes, links, link, node); } catch (error) { - console.error("Error in updateGraph:", error); - errorMessage = `Error updating graph: ${error instanceof Error ? error.message : String(error)}`; + handleGraphError(error); } } @@ -409,14 +937,22 @@ if (svgGroup) { svgGroup .selectAll("g.node") - .select("circle.visual-circle") - .attr("fill", (d: NetworkNode) => - !d.isContainer - ? newIsDarkMode - ? CONTENT_COLOR_DARK - : CONTENT_COLOR_LIGHT - : getEventColor(d.id), - ); + .select(".visual-shape") + .style("fill", (d: NetworkNode) => { + // Person anchors - color based on source + if (d.isPersonAnchor) { + // If from follow list, use kind 3 color + if (d.isFromFollowList) { + return getEventKindColor(3); + } + // Otherwise green for event authors + return "#10B981"; + } + if (d.isTagAnchor) { + return getTagAnchorColor(d.tagType || ""); + } + return getEventKindColor(d.kind); + }); } } } @@ -457,25 +993,183 @@ /** * Watch for changes that should trigger a graph update */ + let isUpdating = false; + let updateTimer: ReturnType | null = null; + + // Create a derived state that combines all dependencies + const graphDependencies = $derived({ + levels: currentLevels, + star: starVisualization, + tags: showTagAnchors, + tagType: selectedTagType, + disabled: disabledTags.size, + persons: showPersonNodes, + disabledPersons: disabledPersons.size, + showSignedBy, + showReferenced, + eventsLength: events?.length || 0 + }); + + // Debounced update function + function scheduleGraphUpdate() { + if (updateTimer) { + clearTimeout(updateTimer); + } + + updateTimer = setTimeout(() => { + if (!isUpdating && svg && events?.length > 0) { + debug("Scheduled graph update executing", graphDependencies); + isUpdating = true; + try { + updateGraph(); + } catch (error) { + console.error("Error updating graph:", error); + errorMessage = `Error updating graph: ${error instanceof Error ? error.message : String(error)}`; + } finally { + isUpdating = false; + updateTimer = null; + } + } + }, 100); // 100ms debounce + } + $effect(() => { - debug("Effect triggered", { - hasSvg: !!svg, - eventCount: events?.length, - currentLevels, - }); + // Just track the dependencies and schedule update + const deps = graphDependencies; + + if (svg && events?.length > 0) { + scheduleGraphUpdate(); + } + }); - try { - if (svg && events?.length) { - // Include currentLevels in the effect dependencies - const _ = currentLevels; - updateGraph(); + // Track previous values to avoid unnecessary calls + let previousTagType = $state(undefined); + let isInitialized = $state(false); + + // Mark as initialized after first render + $effect(() => { + if (!isInitialized && svg) { + isInitialized = true; + } + }); + + /** + * Watch for tag expansion changes + */ + $effect(() => { + // Skip if not initialized or no callback + if (!isInitialized || !onTagExpansionChange) return; + + // Check if we need to trigger expansion + const tagTypeChanged = selectedTagType !== previousTagType; + const shouldExpand = showTagAnchors && tagTypeChanged; + + if (shouldExpand) { + previousTagType = selectedTagType; + + // Extract unique tags from current events + const tags = new Set(); + events.forEach((event: NDKEvent) => { + const eventTags = event.getMatchingTags(selectedTagType); + eventTags.forEach((tag: string[]) => { + if (tag[1]) tags.add(tag[1]); + }); + }); + + debug("Tag expansion requested", { + tagType: selectedTagType, + tags: Array.from(tags), + tagTypeChanged + }); + + onTagExpansionChange(Array.from(tags)); + } + }); + + /** + * Watch for tag anchor count and auto-disable if exceeds threshold + */ + let autoDisableTimer: ReturnType | null = null; + + $effect(() => { + // Clear any pending timer + if (autoDisableTimer) { + clearTimeout(autoDisableTimer); + autoDisableTimer = null; + } + + // Only check when tag anchors are shown and we have tags + if (showTagAnchors && tagAnchorInfo.length > 0) { + + // If we have more than MAX_TAG_ANCHORS and haven't auto-disabled yet + if (tagAnchorInfo.length > MAX_TAG_ANCHORS && !autoDisabledTags) { + // Defer the state update to break the sync cycle + autoDisableTimer = setTimeout(() => { + debug(`Auto-disabling tags: ${tagAnchorInfo.length} exceeds maximum of ${MAX_TAG_ANCHORS}`); + + // Disable all tags + const newDisabledTags = new Set(); + tagAnchorInfo.forEach(anchor => { + const tagId = `${anchor.type}-${anchor.label}`; + newDisabledTags.add(tagId); + }); + + disabledTags = newDisabledTags; + autoDisabledTags = true; + + // Optional: Show a notification to the user + console.info(`[EventNetwork] Auto-disabled ${tagAnchorInfo.length} tag anchors to prevent graph overload. Click individual tags in the legend to enable them.`); + }, 0); + } + + // Reset auto-disabled flag if tag count goes back down + if (tagAnchorInfo.length <= MAX_TAG_ANCHORS && autoDisabledTags) { + autoDisableTimer = setTimeout(() => { + autoDisabledTags = false; + }, 0); } - } catch (error) { - console.error("Error in effect:", error); - errorMessage = `Error updating graph: ${error instanceof Error ? error.message : String(error)}`; + } + + // Reset when tag anchors are hidden + if (!showTagAnchors && autoDisabledTags) { + autoDisableTimer = setTimeout(() => { + autoDisabledTags = false; + }, 0); } }); + /** + * Handles toggling tag visibility + */ + function handleTagToggle(tagId: string) { + if (disabledTags.has(tagId)) { + const newDisabledTags = new Set(disabledTags); + newDisabledTags.delete(tagId); + disabledTags = newDisabledTags; + } else { + const newDisabledTags = new Set(disabledTags); + newDisabledTags.add(tagId); + disabledTags = newDisabledTags; + } + // Update graph will be triggered by the effect + } + + /** + * Handles toggling person node visibility + */ + function handlePersonToggle(pubkey: string) { + if (disabledPersons.has(pubkey)) { + const newDisabledPersons = new Set(disabledPersons); + newDisabledPersons.delete(pubkey); + disabledPersons = newDisabledPersons; + } else { + const newDisabledPersons = new Set(disabledPersons); + newDisabledPersons.add(pubkey); + disabledPersons = newDisabledPersons; + } + // Update graph will be triggered by the effect + } + /** * Handles tooltip close event */ @@ -531,6 +1225,7 @@ graphInteracted = true; } } +
    @@ -551,10 +1246,50 @@ {/if}
    - + { + // Trigger graph update when tag settings change + if (svg && events?.length) { + updateGraph(); + } + }} + bind:showPersonNodes + personAnchors={personAnchorInfo} + {disabledPersons} + onPersonToggle={handlePersonToggle} + onPersonSettingsChange={() => { + // Trigger graph update when person settings change + if (svg && events?.length) { + updateGraph(); + } + }} + bind:showSignedBy + bind:showReferenced + {totalPersonCount} + {displayedPersonCount} + /> - + @@ -641,6 +1376,7 @@ x={tooltipX} y={tooltipY} onclose={handleTooltipClose} + starMode={starVisualization} /> {/if}
    diff --git a/src/lib/navigator/EventNetwork/types.ts b/src/lib/navigator/EventNetwork/types.ts index 1667a3a..67fe49f 100644 --- a/src/lib/navigator/EventNetwork/types.ts +++ b/src/lib/navigator/EventNetwork/types.ts @@ -43,10 +43,22 @@ export interface NetworkNode extends SimulationNodeDatum { title: string; // Event title content: string; // Event content author: string; // Author's public key - type: "Index" | "Content"; // Node type classification + type: "Index" | "Content" | "TagAnchor" | "PersonAnchor"; // Node type classification naddr?: string; // NIP-19 naddr identifier nevent?: string; // NIP-19 nevent identifier isContainer?: boolean; // Whether this node is a container (index) + + // Tag anchor specific fields + isTagAnchor?: boolean; // Whether this is a tag anchor node + tagType?: string; // Type of tag (t, p, e, etc.) + tagValue?: string; // The tag value + connectedNodes?: string[]; // IDs of nodes that have this tag + + // Person anchor specific fields + isPersonAnchor?: boolean; // Whether this is a person anchor node + pubkey?: string; // The person's public key + displayName?: string; // The person's display name from kind 0 + isFromFollowList?: boolean; // Whether this person comes from follow lists } /** diff --git a/src/lib/navigator/EventNetwork/utils/common.ts b/src/lib/navigator/EventNetwork/utils/common.ts new file mode 100644 index 0000000..f8c0bef --- /dev/null +++ b/src/lib/navigator/EventNetwork/utils/common.ts @@ -0,0 +1,41 @@ +/** + * Common utilities shared across network builders + */ + +/** + * Seeded random number generator for deterministic layouts + */ +export class SeededRandom { + private seed: number; + + constructor(seed: number) { + this.seed = seed; + } + + next(): number { + const x = Math.sin(this.seed++) * 10000; + return x - Math.floor(x); + } + + nextFloat(min: number, max: number): number { + return min + this.next() * (max - min); + } + + nextInt(min: number, max: number): number { + return Math.floor(this.nextFloat(min, max + 1)); + } +} + +/** + * Creates a debug function with a prefix + * @param prefix - The prefix to add to all debug messages + * @returns A debug function that can be toggled on/off + */ +export function createDebugFunction(prefix: string) { + const DEBUG = false; + return function debug(...args: any[]) { + if (DEBUG) { + console.log(`[${prefix}]`, ...args); + } + }; +} \ No newline at end of file diff --git a/src/lib/navigator/EventNetwork/utils/forceSimulation.ts b/src/lib/navigator/EventNetwork/utils/forceSimulation.ts index 6eb0dd3..d74ba1d 100644 --- a/src/lib/navigator/EventNetwork/utils/forceSimulation.ts +++ b/src/lib/navigator/EventNetwork/utils/forceSimulation.ts @@ -1,45 +1,38 @@ -// deno-lint-ignore-file no-explicit-any /** * D3 Force Simulation Utilities - * + * * This module provides utilities for creating and managing D3 force-directed * graph simulations for the event network visualization. */ -import type { NetworkNode, NetworkLink } from "../types.ts"; +import type { NetworkNode, NetworkLink } from "../types"; import * as d3 from "d3"; +import { createDebugFunction } from "./common"; // Configuration -const DEBUG = false; // Set to true to enable debug logging const GRAVITY_STRENGTH = 0.05; // Strength of global gravity const CONNECTED_GRAVITY_STRENGTH = 0.3; // Strength of gravity between connected nodes -/** - * Debug logging function that only logs when DEBUG is true - */ -function debug(...args: any[]) { - if (DEBUG) { - console.log("[ForceSimulation]", ...args); - } -} +// Debug function +const debug = createDebugFunction("ForceSimulation"); /** * Type definition for D3 force simulation * Provides type safety for simulation operations */ export interface Simulation { - nodes(): NodeType[]; - nodes(nodes: NodeType[]): this; - alpha(): number; - alpha(alpha: number): this; - alphaTarget(): number; - alphaTarget(target: number): this; - restart(): this; - stop(): this; - tick(): this; - on(type: string, listener: (this: this) => void): this; - force(name: string): any; - force(name: string, force: any): this; + nodes(): NodeType[]; + nodes(nodes: NodeType[]): this; + alpha(): number; + alpha(alpha: number): this; + alphaTarget(): number; + alphaTarget(target: number): this; + restart(): this; + stop(): this; + tick(): this; + on(type: string, listener: (this: this) => void): this; + force(name: string): any; + force(name: string, force: any): this; } /** @@ -47,173 +40,175 @@ export interface Simulation { * Provides type safety for drag operations */ export interface D3DragEvent { - active: number; - sourceEvent: any; - subject: Subject; - x: number; - y: number; - dx: number; - dy: number; - identifier: string | number; + active: number; + sourceEvent: any; + subject: Subject; + x: number; + y: number; + dx: number; + dy: number; + identifier: string | number; } /** * Updates a node's velocity by applying a force - * + * * @param node - The node to update * @param deltaVx - Change in x velocity * @param deltaVy - Change in y velocity */ export function updateNodeVelocity( - node: NetworkNode, - deltaVx: number, - deltaVy: number, + node: NetworkNode, + deltaVx: number, + deltaVy: number ) { - debug("Updating node velocity", { - nodeId: node.id, - currentVx: node.vx, - currentVy: node.vy, - deltaVx, - deltaVy, - }); - - if (typeof node.vx === "number" && typeof node.vy === "number") { - node.vx = node.vx - deltaVx; - node.vy = node.vy - deltaVy; - debug("New velocity", { nodeId: node.id, vx: node.vx, vy: node.vy }); - } else { - debug("Node velocity not defined", { nodeId: node.id }); - } + debug("Updating node velocity", { + nodeId: node.id, + currentVx: node.vx, + currentVy: node.vy, + deltaVx, + deltaVy + }); + + if (typeof node.vx === "number" && typeof node.vy === "number") { + node.vx = node.vx - deltaVx; + node.vy = node.vy - deltaVy; + debug("New velocity", { nodeId: node.id, vx: node.vx, vy: node.vy }); + } else { + debug("Node velocity not defined", { nodeId: node.id }); + } } /** * Applies a logarithmic gravity force pulling the node toward the center - * + * * The logarithmic scale ensures that nodes far from the center experience * stronger gravity, preventing them from drifting too far away. - * + * * @param node - The node to apply gravity to * @param centerX - X coordinate of the center * @param centerY - Y coordinate of the center * @param alpha - Current simulation alpha (cooling factor) */ export function applyGlobalLogGravity( - node: NetworkNode, - centerX: number, - centerY: number, - alpha: number, + node: NetworkNode, + centerX: number, + centerY: number, + alpha: number, ) { - const dx = (node.x ?? 0) - centerX; - const dy = (node.y ?? 0) - centerY; - const distance = Math.sqrt(dx * dx + dy * dy); + // Tag anchors and person anchors should not be affected by gravity + if (node.isTagAnchor || node.isPersonAnchor) return; + + const dx = (node.x ?? 0) - centerX; + const dy = (node.y ?? 0) - centerY; + const distance = Math.sqrt(dx * dx + dy * dy); - if (distance === 0) return; + if (distance === 0) return; - const force = Math.log(distance + 1) * GRAVITY_STRENGTH * alpha; - updateNodeVelocity(node, (dx / distance) * force, (dy / distance) * force); + const force = Math.log(distance + 1) * GRAVITY_STRENGTH * alpha; + updateNodeVelocity(node, (dx / distance) * force, (dy / distance) * force); } /** * Applies gravity between connected nodes - * + * * This creates a cohesive force that pulls connected nodes toward their * collective center of gravity, creating more meaningful clusters. - * + * * @param node - The node to apply connected gravity to * @param links - All links in the network * @param alpha - Current simulation alpha (cooling factor) */ export function applyConnectedGravity( - node: NetworkNode, - links: NetworkLink[], - alpha: number, + node: NetworkNode, + links: NetworkLink[], + alpha: number, ) { - // Find all nodes connected to this node - const connectedNodes = links - .filter((link) => link.source.id === node.id || link.target.id === node.id) - .map((link) => (link.source.id === node.id ? link.target : link.source)); + // Tag anchors and person anchors should not be affected by connected gravity + if (node.isTagAnchor || node.isPersonAnchor) return; + + // Find all nodes connected to this node (excluding tag anchors and person anchors) + const connectedNodes = links + .filter(link => link.source.id === node.id || link.target.id === node.id) + .map(link => link.source.id === node.id ? link.target : link.source) + .filter(n => !n.isTagAnchor && !n.isPersonAnchor); - if (connectedNodes.length === 0) return; + if (connectedNodes.length === 0) return; - // Calculate center of gravity of connected nodes - const cogX = d3.mean(connectedNodes, (n: NetworkNode) => n.x); - const cogY = d3.mean(connectedNodes, (n: NetworkNode) => n.y); + // Calculate center of gravity of connected nodes + const cogX = d3.mean(connectedNodes, (n: NetworkNode) => n.x); + const cogY = d3.mean(connectedNodes, (n: NetworkNode) => n.y); - if (cogX === undefined || cogY === undefined) return; + if (cogX === undefined || cogY === undefined) return; - // Calculate force direction and magnitude - const dx = (node.x ?? 0) - cogX; - const dy = (node.y ?? 0) - cogY; - const distance = Math.sqrt(dx * dx + dy * dy); + // Calculate force direction and magnitude + const dx = (node.x ?? 0) - cogX; + const dy = (node.y ?? 0) - cogY; + const distance = Math.sqrt(dx * dx + dy * dy); - if (distance === 0) return; + if (distance === 0) return; - // Apply force proportional to distance - const force = distance * CONNECTED_GRAVITY_STRENGTH * alpha; - updateNodeVelocity(node, (dx / distance) * force, (dy / distance) * force); + // Apply force proportional to distance + const force = distance * CONNECTED_GRAVITY_STRENGTH * alpha; + updateNodeVelocity(node, (dx / distance) * force, (dy / distance) * force); } /** * Sets up drag behavior for nodes - * + * * This enables interactive dragging of nodes in the visualization. - * + * * @param simulation - The D3 force simulation * @param warmupClickEnergy - Alpha target when dragging starts (0-1) * @returns D3 drag behavior configured for the simulation */ export function setupDragHandlers( - simulation: Simulation, - warmupClickEnergy: number = 0.9, + simulation: Simulation, + warmupClickEnergy: number = 0.9 ) { - return d3 - .drag() - .on( - "start", - ( - event: D3DragEvent, - d: NetworkNode, - ) => { - // Warm up simulation if it's cooled down - if (!event.active) { - simulation.alphaTarget(warmupClickEnergy).restart(); - } - // Fix node position at current location - d.fx = d.x; - d.fy = d.y; - }, - ) - .on( - "drag", - ( - event: D3DragEvent, - d: NetworkNode, - ) => { - // Update fixed position to mouse position - d.fx = event.x; - d.fy = event.y; - }, - ) - .on( - "end", - ( - event: D3DragEvent, - d: NetworkNode, - ) => { - // Cool down simulation when drag ends - if (!event.active) { - simulation.alphaTarget(0); - } - // Release fixed position - d.fx = null; - d.fy = null; - }, - ); + return d3 + .drag() + .on("start", (event: D3DragEvent, d: NetworkNode) => { + // Tag anchors and person anchors retain their anchor behavior + if (d.isTagAnchor || d.isPersonAnchor) { + // Still allow dragging but maintain anchor status + d.fx = d.x; + d.fy = d.y; + return; + } + + // Warm up simulation if it's cooled down + if (!event.active) { + simulation.alphaTarget(warmupClickEnergy).restart(); + } + // Fix node position at current location + d.fx = d.x; + d.fy = d.y; + }) + .on("drag", (event: D3DragEvent, d: NetworkNode) => { + // Update position for all nodes including anchors + + // Update fixed position to mouse position + d.fx = event.x; + d.fy = event.y; + }) + .on("end", (event: D3DragEvent, d: NetworkNode) => { + + // Cool down simulation when drag ends + if (!event.active) { + simulation.alphaTarget(0); + } + + // Keep all nodes fixed after dragging + // This allows users to manually position any node type + d.fx = d.x; + d.fy = d.y; + }); } /** * Creates a D3 force simulation for the network - * + * * @param nodes - Array of network nodes * @param links - Array of network links * @param nodeRadius - Radius of node circles @@ -221,35 +216,34 @@ export function setupDragHandlers( * @returns Configured D3 force simulation */ export function createSimulation( - nodes: NetworkNode[], - links: NetworkLink[], - nodeRadius: number, - linkDistance: number, + nodes: NetworkNode[], + links: NetworkLink[], + nodeRadius: number, + linkDistance: number ): Simulation { - debug("Creating simulation", { - nodeCount: nodes.length, - linkCount: links.length, - nodeRadius, - linkDistance, - }); - - try { - // Create the simulation with nodes - const simulation = d3 - .forceSimulation(nodes) - .force( - "link", - d3 - .forceLink(links) - .id((d: NetworkNode) => d.id) - .distance(linkDistance * 0.1), - ) - .force("collide", d3.forceCollide().radius(nodeRadius * 4)); - - debug("Simulation created successfully"); - return simulation; - } catch (error) { - console.error("Error creating simulation:", error); - throw error; - } + debug("Creating simulation", { + nodeCount: nodes.length, + linkCount: links.length, + nodeRadius, + linkDistance + }); + + try { + // Create the simulation with nodes + const simulation = d3 + .forceSimulation(nodes) + .force( + "link", + d3.forceLink(links) + .id((d: NetworkNode) => d.id) + .distance(linkDistance * 0.1) + ) + .force("collide", d3.forceCollide().radius(nodeRadius * 4)); + + debug("Simulation created successfully"); + return simulation; + } catch (error) { + console.error("Error creating simulation:", error); + throw error; + } } diff --git a/src/lib/navigator/EventNetwork/utils/networkBuilder.ts b/src/lib/navigator/EventNetwork/utils/networkBuilder.ts index aa234b2..3ba3abd 100644 --- a/src/lib/navigator/EventNetwork/utils/networkBuilder.ts +++ b/src/lib/navigator/EventNetwork/utils/networkBuilder.ts @@ -1,207 +1,186 @@ /** * Network Builder Utilities - * + * * This module provides utilities for building a network graph from Nostr events. * It handles the creation of nodes and links, and the processing of event relationships. */ import type { NDKEvent } from "@nostr-dev-kit/ndk"; -import type { NetworkNode, GraphData, GraphState } from "../types.ts"; +import type { NetworkNode, NetworkLink, GraphData, GraphState } from "../types"; import { nip19 } from "nostr-tools"; -import { activeInboxRelays, activeOutboxRelays } from "../../../ndk.ts"; -import { getMatchingTags } from "../../../utils/nostrUtils.ts"; -import { get } from "svelte/store"; +import { communityRelays } from "$lib/consts"; +import { getMatchingTags } from '$lib/utils/nostrUtils'; +import { getDisplayNameSync } from '$lib/utils/profileCache'; +import { createDebugFunction } from "./common"; // Configuration -const DEBUG = false; // Set to true to enable debug logging const INDEX_EVENT_KIND = 30040; const CONTENT_EVENT_KIND = 30041; -/** - * Debug logging function that only logs when DEBUG is true - */ -function debug(...args: unknown[]) { - if (DEBUG) { - console.log("[NetworkBuilder]", ...args); - } -} +// Debug function +const debug = createDebugFunction("NetworkBuilder"); /** * Creates a NetworkNode from an NDKEvent - * + * * Extracts relevant information from the event and creates a node representation * for the visualization. - * + * * @param event - The Nostr event to convert to a node * @param level - The hierarchy level of the node (default: 0) * @returns A NetworkNode object representing the event */ export function createNetworkNode( - event: NDKEvent, - level: number = 0, + event: NDKEvent, + level: number = 0 ): NetworkNode { - debug("Creating network node", { - eventId: event.id, - kind: event.kind, - level, - }); - - const isContainer = event.kind === INDEX_EVENT_KIND; - const nodeType = isContainer ? "Index" : "Content"; - - // Create the base node with essential properties - const node: NetworkNode = { - id: event.id, - event, - isContainer, - level, - title: event.getMatchingTags("title")?.[0]?.[1] || "Untitled", - content: event.content || "", - author: event.pubkey || "", - kind: event.kind || CONTENT_EVENT_KIND, // Default to content event kind if undefined - type: nodeType, - }; + debug("Creating network node", { eventId: event.id, kind: event.kind, level }); + + const isContainer = event.kind === INDEX_EVENT_KIND; + const nodeType = isContainer ? "Index" : event.kind === CONTENT_EVENT_KIND || event.kind === 30818 ? "Content" : `Kind ${event.kind}`; - // Add NIP-19 identifiers if possible - if (event.kind && event.pubkey) { - try { - const dTag = event.getMatchingTags("d")?.[0]?.[1] || ""; - - // Create naddr (NIP-19 address) for the event - node.naddr = nip19.naddrEncode({ - pubkey: event.pubkey, - identifier: dTag, - kind: event.kind, - relays: [...get(activeInboxRelays), ...get(activeOutboxRelays)], - }); - - // Create nevent (NIP-19 event reference) for the event - node.nevent = nip19.neventEncode({ + // Create the base node with essential properties + const node: NetworkNode = { id: event.id, - relays: [...get(activeInboxRelays), ...get(activeOutboxRelays)], - kind: event.kind, - }); - } catch (error) { - console.warn("Failed to generate identifiers for node:", error); + event, + isContainer, + level, + title: event.getMatchingTags("title")?.[0]?.[1] || "Untitled", + content: event.content || "", + author: event.pubkey ? getDisplayNameSync(event.pubkey) : "", + kind: event.kind !== undefined ? event.kind : CONTENT_EVENT_KIND, // Default to content event kind only if truly undefined + type: nodeType as "Index" | "Content" | "TagAnchor", + }; + + // Add NIP-19 identifiers if possible + if (event.kind && event.pubkey) { + try { + const dTag = event.getMatchingTags("d")?.[0]?.[1] || ""; + + // Create naddr (NIP-19 address) for the event + node.naddr = nip19.naddrEncode({ + pubkey: event.pubkey, + identifier: dTag, + kind: event.kind, + relays: communityRelays, + }); + + // Create nevent (NIP-19 event reference) for the event + node.nevent = nip19.neventEncode({ + id: event.id, + relays: communityRelays, + kind: event.kind, + }); + } catch (error) { + console.warn("Failed to generate identifiers for node:", error); + } } - } - return node; + return node; } /** * Creates a map of event IDs to events for quick lookup - * + * * @param events - Array of Nostr events * @returns Map of event IDs to events */ export function createEventMap(events: NDKEvent[]): Map { - debug("Creating event map", { eventCount: events.length }); - - const eventMap = new Map(); - events.forEach((event) => { - if (event.id) { - eventMap.set(event.id, event); - } - }); - - debug("Event map created", { mapSize: eventMap.size }); - return eventMap; + debug("Creating event map", { eventCount: events.length }); + + const eventMap = new Map(); + events.forEach((event) => { + if (event.id) { + eventMap.set(event.id, event); + } + }); + + debug("Event map created", { mapSize: eventMap.size }); + return eventMap; } /** * Extracts an event ID from an 'a' tag - * + * * @param tag - The tag array from a Nostr event * @returns The event ID or null if not found */ export function extractEventIdFromATag(tag: string[]): string | null { - return tag[3] || null; + return tag[3] || null; } /** * Generates a deterministic color for an event based on its ID - * + * * This creates visually distinct colors for different index events * while ensuring the same event always gets the same color. - * + * * @param eventId - The event ID to generate a color for * @returns An HSL color string */ export function getEventColor(eventId: string): string { - // Use first 4 characters of event ID as a hex number - const num = parseInt(eventId.slice(0, 4), 16); - // Convert to a hue value (0-359) - const hue = num % 360; - // Use fixed saturation and lightness for pastel colors - const saturation = 70; - const lightness = 75; - return `hsl(${hue}, ${saturation}%, ${lightness}%)`; + // Use first 4 characters of event ID as a hex number + const num = parseInt(eventId.slice(0, 4), 16); + // Convert to a hue value (0-359) + const hue = num % 360; + // Use fixed saturation and lightness for pastel colors + const saturation = 70; + const lightness = 75; + return `hsl(${hue}, ${saturation}%, ${lightness}%)`; } /** * Initializes the graph state from a set of events - * + * * Creates nodes for all events and identifies referenced events. - * + * * @param events - Array of Nostr events * @returns Initial graph state */ export function initializeGraphState(events: NDKEvent[]): GraphState { - debug("Initializing graph state", { eventCount: events.length }); - - const nodeMap = new Map(); - const eventMap = createEventMap(events); - - // Create initial nodes for all events - events.forEach((event) => { - if (!event.id) return; - const node = createNetworkNode(event); - nodeMap.set(event.id, node); - }); - debug("Node map created", { nodeCount: nodeMap.size }); - - // Build set of referenced event IDs to identify root events - const referencedIds = new Set(); - events.forEach((event) => { - // Handle both "a" tags (NIP-62) and "e" tags (legacy) - let tags = getMatchingTags(event, "a"); - if (tags.length === 0) { - tags = getMatchingTags(event, "e"); - } - - debug("Processing tags for event", { - eventId: event.id, - tagCount: tags.length, - tagType: - tags.length > 0 - ? getMatchingTags(event, "a").length > 0 - ? "a" - : "e" - : "none", + debug("Initializing graph state", { eventCount: events.length }); + + const nodeMap = new Map(); + const eventMap = createEventMap(events); + + // Create initial nodes for all events + events.forEach((event) => { + if (!event.id) return; + const node = createNetworkNode(event); + nodeMap.set(event.id, node); }); - - tags.forEach((tag) => { - const id = extractEventIdFromATag(tag); - if (id) referencedIds.add(id); + debug("Node map created", { nodeCount: nodeMap.size }); + + // Build set of referenced event IDs to identify root events + const referencedIds = new Set(); + events.forEach((event) => { + const aTags = getMatchingTags(event, "a"); + debug("Processing a-tags for event", { + eventId: event.id, + aTagCount: aTags.length + }); + + aTags.forEach((tag) => { + const id = extractEventIdFromATag(tag); + if (id) referencedIds.add(id); + }); }); - }); - debug("Referenced IDs set created", { referencedCount: referencedIds.size }); - - return { - nodeMap, - links: [], - eventMap, - referencedIds, - }; + debug("Referenced IDs set created", { referencedCount: referencedIds.size }); + + return { + nodeMap, + links: [], + eventMap, + referencedIds, + }; } /** * Processes a sequence of nodes referenced by an index event - * + * * Creates links between the index and its content, and between sequential content nodes. * Also processes nested indices recursively up to the maximum level. - * + * * @param sequence - Array of nodes in the sequence * @param indexEvent - The index event referencing the sequence * @param level - Current hierarchy level @@ -209,153 +188,156 @@ export function initializeGraphState(events: NDKEvent[]): GraphState { * @param maxLevel - Maximum hierarchy level to process */ export function processSequence( - sequence: NetworkNode[], - indexEvent: NDKEvent, - level: number, - state: GraphState, - maxLevel: number, + sequence: NetworkNode[], + indexEvent: NDKEvent, + level: number, + state: GraphState, + maxLevel: number, ): void { - // Stop if we've reached max level or have no nodes - if (level >= maxLevel || sequence.length === 0) return; - - // Set levels for all nodes in the sequence - sequence.forEach((node) => { - node.level = level + 1; - }); + // Stop if we've reached max level or have no nodes + if (level >= maxLevel || sequence.length === 0) return; - // Create link from index to first content node - const indexNode = state.nodeMap.get(indexEvent.id); - if (indexNode && sequence[0]) { - state.links.push({ - source: indexNode, - target: sequence[0], - isSequential: true, + // Set levels for all nodes in the sequence + sequence.forEach((node) => { + node.level = level + 1; }); - } - // Create sequential links between content nodes - for (let i = 0; i < sequence.length - 1; i++) { - const currentNode = sequence[i]; - const nextNode = sequence[i + 1]; - - state.links.push({ - source: currentNode, - target: nextNode, - isSequential: true, - }); + // Create link from index to first content node + const indexNode = state.nodeMap.get(indexEvent.id); + if (indexNode && sequence[0]) { + state.links.push({ + source: indexNode, + target: sequence[0], + isSequential: true, + }); + } - // Process nested indices recursively - if (currentNode.isContainer) { - processNestedIndex(currentNode, level + 1, state, maxLevel); + // Create sequential links between content nodes + for (let i = 0; i < sequence.length - 1; i++) { + const currentNode = sequence[i]; + const nextNode = sequence[i + 1]; + + state.links.push({ + source: currentNode, + target: nextNode, + isSequential: true, + }); + + // Process nested indices recursively + if (currentNode.isContainer) { + processNestedIndex(currentNode, level + 1, state, maxLevel); + } } - } - // Process the last node if it's an index - const lastNode = sequence[sequence.length - 1]; - if (lastNode?.isContainer) { - processNestedIndex(lastNode, level + 1, state, maxLevel); - } + // Process the last node if it's an index + const lastNode = sequence[sequence.length - 1]; + if (lastNode?.isContainer) { + processNestedIndex(lastNode, level + 1, state, maxLevel); + } } /** * Processes a nested index node - * + * * @param node - The index node to process * @param level - Current hierarchy level * @param state - Current graph state * @param maxLevel - Maximum hierarchy level to process */ export function processNestedIndex( - node: NetworkNode, - level: number, - state: GraphState, - maxLevel: number, + node: NetworkNode, + level: number, + state: GraphState, + maxLevel: number, ): void { - if (!node.isContainer || level >= maxLevel) return; + if (!node.isContainer || level >= maxLevel) return; - const nestedEvent = state.eventMap.get(node.id); - if (nestedEvent) { - processIndexEvent(nestedEvent, level, state, maxLevel); - } + const nestedEvent = state.eventMap.get(node.id); + if (nestedEvent) { + processIndexEvent(nestedEvent, level, state, maxLevel); + } } /** * Processes an index event and its referenced content - * + * * @param indexEvent - The index event to process * @param level - Current hierarchy level * @param state - Current graph state * @param maxLevel - Maximum hierarchy level to process */ export function processIndexEvent( - indexEvent: NDKEvent, - level: number, - state: GraphState, - maxLevel: number, + indexEvent: NDKEvent, + level: number, + state: GraphState, + maxLevel: number, ): void { - if (level >= maxLevel) return; - - // Extract the sequence of nodes referenced by this index - // Handle both "a" tags (NIP-62) and "e" tags (legacy) - let tags = getMatchingTags(indexEvent, "a"); - if (tags.length === 0) { - tags = getMatchingTags(indexEvent, "e"); - } + if (level >= maxLevel) return; - const sequence = tags - .map((tag) => extractEventIdFromATag(tag)) - .filter((id): id is string => id !== null) - .map((id) => state.nodeMap.get(id)) - .filter((node): node is NetworkNode => node !== undefined); + // Extract the sequence of nodes referenced by this index + const sequence = getMatchingTags(indexEvent, "a") + .map((tag) => extractEventIdFromATag(tag)) + .filter((id): id is string => id !== null) + .map((id) => state.nodeMap.get(id)) + .filter((node): node is NetworkNode => node !== undefined); - processSequence(sequence, indexEvent, level, state, maxLevel); + processSequence(sequence, indexEvent, level, state, maxLevel); } /** * Generates a complete graph from a set of events - * + * * This is the main entry point for building the network visualization. - * + * * @param events - Array of Nostr events * @param maxLevel - Maximum hierarchy level to process * @returns Complete graph data for visualization */ -export function generateGraph(events: NDKEvent[], maxLevel: number): GraphData { - debug("Generating graph", { eventCount: events.length, maxLevel }); - - // Initialize the graph state - const state = initializeGraphState(events); - - // Find root index events (those not referenced by other events) - const rootIndices = events.filter( - (e) => - e.kind === INDEX_EVENT_KIND && e.id && !state.referencedIds.has(e.id), - ); - - debug("Found root indices", { - rootCount: rootIndices.length, - rootIds: rootIndices.map((e) => e.id), - }); - - // Process each root index - rootIndices.forEach((rootIndex) => { - debug("Processing root index", { - rootId: rootIndex.id, - aTags: getMatchingTags(rootIndex, "a").length, +export function generateGraph( + events: NDKEvent[], + maxLevel: number +): GraphData { + debug("Generating graph", { eventCount: events.length, maxLevel }); + + // Initialize the graph state + const state = initializeGraphState(events); + + // Find root events (index events not referenced by others, and all non-publication events) + const publicationKinds = [30040, 30041, 30818]; + const rootEvents = events.filter( + (e) => e.id && ( + // Index events not referenced by others + (e.kind === INDEX_EVENT_KIND && !state.referencedIds.has(e.id)) || + // All non-publication events are treated as roots + (e.kind !== undefined && !publicationKinds.includes(e.kind)) + ) + ); + + debug("Found root events", { + rootCount: rootEvents.length, + rootIds: rootEvents.map(e => e.id) + }); + + // Process each root event + rootEvents.forEach((rootEvent) => { + debug("Processing root event", { + rootId: rootEvent.id, + kind: rootEvent.kind, + aTags: getMatchingTags(rootEvent, "a").length + }); + processIndexEvent(rootEvent, 0, state, maxLevel); }); - processIndexEvent(rootIndex, 0, state, maxLevel); - }); - - // Create the final graph data - const result = { - nodes: Array.from(state.nodeMap.values()), - links: state.links, - }; - - debug("Graph generation complete", { - nodeCount: result.nodes.length, - linkCount: result.links.length, - }); - return result; + // Create the final graph data + const result = { + nodes: Array.from(state.nodeMap.values()), + links: state.links, + }; + + debug("Graph generation complete", { + nodeCount: result.nodes.length, + linkCount: result.links.length + }); + + return result; } diff --git a/src/lib/navigator/EventNetwork/utils/personNetworkBuilder.ts b/src/lib/navigator/EventNetwork/utils/personNetworkBuilder.ts new file mode 100644 index 0000000..aaafa00 --- /dev/null +++ b/src/lib/navigator/EventNetwork/utils/personNetworkBuilder.ts @@ -0,0 +1,339 @@ +/** + * Person Network Builder + * + * Creates person anchor nodes for event authors in the network visualization + */ + +import type { NDKEvent } from "@nostr-dev-kit/ndk"; +import type { NetworkNode, NetworkLink } from "../types"; +import { getDisplayNameSync } from "$lib/utils/profileCache"; +import { SeededRandom, createDebugFunction } from "./common"; + +const PERSON_ANCHOR_RADIUS = 15; +const PERSON_ANCHOR_PLACEMENT_RADIUS = 1000; +const MAX_PERSON_NODES = 20; // Default limit for person nodes + +// Debug function +const debug = createDebugFunction("PersonNetworkBuilder"); + + +/** + * Creates a deterministic seed from a string + */ +function createSeed(str: string): number { + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; + } + return Math.abs(hash); +} + +export interface PersonConnection { + signedByEventIds: Set; + referencedInEventIds: Set; + isFromFollowList?: boolean; // Track if this person comes from follow lists +} + +/** + * Extracts unique persons (pubkeys) from events + * Tracks both signed-by (event.pubkey) and referenced (["p", pubkey] tags) + */ +export function extractUniquePersons( + events: NDKEvent[], + followListEvents?: NDKEvent[] +): Map { + // Map of pubkey -> PersonConnection + const personMap = new Map(); + + debug("Extracting unique persons", { eventCount: events.length, followListCount: followListEvents?.length || 0 }); + + // First collect pubkeys from follow list events + const followListPubkeys = new Set(); + if (followListEvents && followListEvents.length > 0) { + followListEvents.forEach((event) => { + // Follow list author + if (event.pubkey) { + followListPubkeys.add(event.pubkey); + } + // People in follow lists (p tags) + if (event.tags) { + event.tags + .filter(tag => { + tag[0] === 'p' + }) + .forEach(tag => { + followListPubkeys.add(tag[1]); + }); + } + }); + } + + events.forEach((event) => { + if (!event.id) return; + + // Track signed-by connections + if (event.pubkey) { + if (!personMap.has(event.pubkey)) { + personMap.set(event.pubkey, { + signedByEventIds: new Set(), + referencedInEventIds: new Set(), + isFromFollowList: followListPubkeys.has(event.pubkey) + }); + } + personMap.get(event.pubkey)!.signedByEventIds.add(event.id); + } + + // Track referenced connections from "p" tags + if (event.tags) { + event.tags.forEach(tag => { + if (tag[0] === "p" && tag[1]) { + const referencedPubkey = tag[1]; + if (!personMap.has(referencedPubkey)) { + personMap.set(referencedPubkey, { + signedByEventIds: new Set(), + referencedInEventIds: new Set(), + isFromFollowList: followListPubkeys.has(referencedPubkey) + }); + } + personMap.get(referencedPubkey)!.referencedInEventIds.add(event.id); + } + }); + } + }); + + debug("Extracted persons", { personCount: personMap.size }); + + return personMap; +} + +/** + * Helper to build eligible person info for anchor nodes. + */ +function buildEligiblePerson( + pubkey: string, + connection: PersonConnection, + showSignedBy: boolean, + showReferenced: boolean +): { + pubkey: string; + connection: PersonConnection; + connectedEventIds: Set; + totalConnections: number; +} | null { + const connectedEventIds = new Set(); + + if (showSignedBy) { + connection.signedByEventIds.forEach(id => connectedEventIds.add(id)); + } + + if (showReferenced) { + connection.referencedInEventIds.forEach(id => connectedEventIds.add(id)); + } + + if (connectedEventIds.size === 0) { + return null; + } + + return { + pubkey, + connection, + connectedEventIds, + totalConnections: connectedEventIds.size + }; +} + +type EligiblePerson = { + pubkey: string; + connection: PersonConnection; + totalConnections: number; + connectedEventIds: Set; +}; + +function getEligiblePersons( + personMap: Map, + showSignedBy: boolean, + showReferenced: boolean, + limit: number +): EligiblePerson[] { + // Build eligible persons and keep only top N using a min-heap or partial sort + const eligible: EligiblePerson[] = []; + + for (const [pubkey, connection] of personMap) { + let totalConnections = 0; + if (showSignedBy) totalConnections += connection.signedByEventIds.size; + if (showReferenced) totalConnections += connection.referencedInEventIds.size; + if (totalConnections === 0) continue; + + // Only build the set if this person is eligible + const connectedEventIds = new Set(); + if (showSignedBy) { + connection.signedByEventIds.forEach(id => connectedEventIds.add(id)); + } + if (showReferenced) { + connection.referencedInEventIds.forEach(id => connectedEventIds.add(id)); + } + + eligible.push({ pubkey, connection, totalConnections, connectedEventIds }); + } + + // Partial sort: get top N by totalConnections + eligible.sort((a, b) => b.totalConnections - a.totalConnections); + return eligible.slice(0, limit); +} + +/** + * Creates person anchor nodes + */ +export function createPersonAnchorNodes( + personMap: Map, + width: number, + height: number, + showSignedBy: boolean, + showReferenced: boolean, + limit: number = MAX_PERSON_NODES +): { nodes: NetworkNode[], totalCount: number } { + const anchorNodes: NetworkNode[] = []; + + const centerX = width / 2; + const centerY = height / 2; + + // Calculate eligible persons and their connection counts + const eligiblePersons = getEligiblePersons(personMap, showSignedBy, showReferenced, limit); + + // Create nodes for the limited set + debug("Creating person anchor nodes", { + eligibleCount: eligiblePersons.length, + limitedCount: eligiblePersons.length, + showSignedBy, + showReferenced + }); + + eligiblePersons.forEach(({ pubkey, connection, connectedEventIds }) => { + // Create seeded random generator for consistent positioning + const rng = new SeededRandom(createSeed(pubkey)); + + // Generate deterministic position + const angle = rng.next() * 2 * Math.PI; + const distance = rng.next() * PERSON_ANCHOR_PLACEMENT_RADIUS; + const x = centerX + distance * Math.cos(angle); + const y = centerY + distance * Math.sin(angle); + + // Get display name + const displayName = getDisplayNameSync(pubkey); + + const anchorNode: NetworkNode = { + id: `person-anchor-${pubkey}`, + title: displayName, + content: `${connection.signedByEventIds.size} signed, ${connection.referencedInEventIds.size} referenced`, + author: "", + kind: 0, // Special kind for anchors + type: "PersonAnchor", + level: -1, + isPersonAnchor: true, + pubkey, + displayName, + connectedNodes: Array.from(connectedEventIds), + isFromFollowList: connection.isFromFollowList, + x, + y, + fx: x, // Fix position + fy: y, + }; + + anchorNodes.push(anchorNode); + }); + + debug("Created person anchor nodes", { count: anchorNodes.length, totalEligible: eligiblePersons.length }); + + return { + nodes: anchorNodes, + totalCount: eligiblePersons.length + }; +} + +export interface PersonLink extends NetworkLink { + connectionType?: "signed-by" | "referenced"; +} + +/** + * Creates links between person anchors and their events + * Adds connection type for coloring + */ +export function createPersonLinks( + personAnchors: NetworkNode[], + nodes: NetworkNode[], + personMap: Map +): PersonLink[] { + debug("Creating person links", { anchorCount: personAnchors.length, nodeCount: nodes.length }); + + const nodeMap = new Map(nodes.map((n) => [n.id, n])); + + const links: PersonLink[] = personAnchors.flatMap((anchor) => { + if (!anchor.connectedNodes || !anchor.pubkey) { + return []; + } + + const connection = personMap.get(anchor.pubkey); + if (!connection) { + return []; + } + + return anchor.connectedNodes.map((nodeId) => { + const node = nodeMap.get(nodeId); + if (!node) { + return undefined; + } + + let connectionType: 'signed-by' | 'referenced' | undefined; + if (connection.signedByEventIds.has(nodeId)) { + connectionType = 'signed-by'; + } else if (connection.referencedInEventIds.has(nodeId)) { + connectionType = 'referenced'; + } + + const link: PersonLink = { + source: anchor, + target: node, + isSequential: false, + connectionType, + }; + + return link; + }).filter((link): link is PersonLink => link !== undefined); // Remove undefineds and type guard + }); + + debug("Created person links", { linkCount: links.length }); + return links; +} + +/** + * Formats person anchor info for display in Legend + */ +export interface PersonAnchorInfo { + pubkey: string; + displayName: string; + signedByCount: number; + referencedCount: number; + isFromFollowList: boolean; +} + +/** + * Extracts person info for Legend display + */ +export function extractPersonAnchorInfo( + personAnchors: NetworkNode[], + personMap: Map +): PersonAnchorInfo[] { + return personAnchors.map(anchor => { + const connection = personMap.get(anchor.pubkey || ""); + return { + pubkey: anchor.pubkey || "", + displayName: anchor.displayName || "", + signedByCount: connection?.signedByEventIds.size || 0, + referencedCount: connection?.referencedInEventIds.size || 0, + isFromFollowList: connection?.isFromFollowList || false, + }; + }); +} \ No newline at end of file diff --git a/src/lib/navigator/EventNetwork/utils/starForceSimulation.ts b/src/lib/navigator/EventNetwork/utils/starForceSimulation.ts new file mode 100644 index 0000000..c22ac1d --- /dev/null +++ b/src/lib/navigator/EventNetwork/utils/starForceSimulation.ts @@ -0,0 +1,308 @@ +/** + * Star Network Force Simulation + * + * Custom force simulation optimized for star network layouts. + * Provides stronger connections between star centers and their content nodes, + * with specialized forces to maintain hierarchical structure. + */ + +import * as d3 from "d3"; +import type { NetworkNode, NetworkLink } from "../types"; +import type { Simulation } from "./forceSimulation"; +import { createTagGravityForce } from "./tagNetworkBuilder"; + +// Configuration for star network forces +const STAR_CENTER_CHARGE = -300; // Stronger repulsion between star centers +const CONTENT_NODE_CHARGE = -50; // Weaker repulsion for content nodes +const STAR_LINK_STRENGTH = 0.5; // Moderate connection to star center +const INTER_STAR_LINK_STRENGTH = 0.2; // Weaker connection between stars +const STAR_LINK_DISTANCE = 80; // Fixed distance from center to content +const INTER_STAR_DISTANCE = 200; // Distance between star centers +const CENTER_GRAVITY = 0.02; // Gentle pull toward canvas center +const STAR_CENTER_WEIGHT = 10; // Weight multiplier for star centers + +/** + * Creates a custom force simulation for star networks + */ +export function createStarSimulation( + nodes: NetworkNode[], + links: NetworkLink[], + width: number, + height: number +): Simulation { + // Create the simulation + const simulation = d3.forceSimulation(nodes) as any + simulation + .force("center", d3.forceCenter(width / 2, height / 2).strength(CENTER_GRAVITY)) + .velocityDecay(0.2) // Lower decay for more responsive simulation + .alphaDecay(0.0001) // Much slower alpha decay to prevent freezing + .alphaMin(0.001); // Keep minimum energy to prevent complete freeze + + // Custom charge force that varies by node type + const chargeForce = d3.forceManyBody() + .strength((d: NetworkNode) => { + // Tag anchors don't repel + if (d.isTagAnchor) { + return 0; + } + // Star centers repel each other strongly + if (d.isContainer && d.kind === 30040) { + return STAR_CENTER_CHARGE; + } + // Content nodes have minimal repulsion + return CONTENT_NODE_CHARGE; + }) + .distanceMax(300); // Limit charge force range + + // Custom link force with variable strength and distance + const linkForce = d3.forceLink(links) + .id((d: NetworkNode) => d.id) + .strength((link: any) => { + const source = link.source as NetworkNode; + const target = link.target as NetworkNode; + // Strong connection from star center to its content + if (source.kind === 30040 && target.kind === 30041) { + return STAR_LINK_STRENGTH; + } + // Weaker connection between star centers + if (source.kind === 30040 && target.kind === 30040) { + return INTER_STAR_LINK_STRENGTH; + } + return 0.5; // Default strength + }) + .distance((link: any) => { + const source = link.source as NetworkNode; + const target = link.target as NetworkNode; + // Fixed distance for star-to-content links + if (source.kind === 30040 && target.kind === 30041) { + return STAR_LINK_DISTANCE; + } + // Longer distance between star centers + if (source.kind === 30040 && target.kind === 30040) { + return INTER_STAR_DISTANCE; + } + return 100; // Default distance + }); + + // Apply forces to simulation + simulation + .force("charge", chargeForce) + .force("link", linkForce); + + // Custom radial force to keep content nodes around their star center + simulation.force("radial", createRadialForce(nodes, links)); + + // Add tag gravity force if there are tag anchors + const hasTagAnchors = nodes.some(n => n.isTagAnchor); + if (hasTagAnchors) { + simulation.force("tagGravity", createTagGravityForce(nodes, links)); + } + + // Periodic reheat to prevent freezing + let tickCount = 0; + simulation.on("tick", () => { + tickCount++; + // Every 300 ticks, give a small energy boost to prevent freezing + if (tickCount % 300 === 0 && simulation.alpha() < 0.01) { + simulation.alpha(0.02); + } + }); + + return simulation; +} + +/** + * Applies the radial force to keep content nodes in orbit around their star center + * @param nodes - The array of network nodes + * @param nodeToCenter - Map of content node IDs to their star center node + * @param targetDistance - The desired distance from center to content node + * @param alpha - The current simulation alpha + */ +function applyRadialForce( + nodes: NetworkNode[], + nodeToCenter: Map, + targetDistance: number, + alpha: number +): void { + nodes.forEach(node => { + if (node.kind === 30041) { + const center = nodeToCenter.get(node.id); + if ( + center && + center.x != null && + center.y != null && + node.x != null && + node.y != null + ) { + // Calculate desired position + const dx = node.x - center.x; + const dy = node.y - center.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance > 0) { + // Normalize and apply force + const force = (distance - targetDistance) * alpha * 0.3; // Reduced force + node.vx = (node.vx || 0) - (dx / distance) * force; + node.vy = (node.vy || 0) - (dy / distance) * force; + } + } + } + }); +} + +/** + * Creates a custom radial force that keeps content nodes in orbit around their star center + */ +function createRadialForce(nodes: NetworkNode[], links: NetworkLink[]): any { + // Build a map of content nodes to their star centers + const nodeToCenter = new Map(); + + links.forEach(link => { + const source = link.source as NetworkNode; + const target = link.target as NetworkNode; + if (source.kind === 30040 && target.kind === 30041) { + nodeToCenter.set(target.id, source); + } + }); + + function force(alpha: number) { + applyRadialForce(nodes, nodeToCenter, STAR_LINK_DISTANCE, alpha); + } + + force.initialize = function(_: NetworkNode[]) { + nodes = _; + }; + + return force; +} + +/** + * Applies initial positioning for star networks + */ +export function applyInitialStarPositions( + nodes: NetworkNode[], + links: NetworkLink[], + width: number, + height: number +): void { + // Group nodes by their star centers + const starGroups = new Map(); + const starCenters: NetworkNode[] = []; + + // Identify star centers + nodes.forEach(node => { + if (node.isContainer && node.kind === 30040) { + starCenters.push(node); + starGroups.set(node.id, []); + } + }); + + // Assign content nodes to their star centers + links.forEach(link => { + const source = link.source as NetworkNode; + const target = link.target as NetworkNode; + if (source.kind === 30040 && target.kind === 30041) { + const group = starGroups.get(source.id); + if (group) { + group.push(target); + } + } + }); + + // Position star centers in a grid or circle + if (starCenters.length === 1) { + // Single star - center it + const center = starCenters[0]; + center.x = width / 2; + center.y = height / 2; + // Don't fix position initially - let simulation run naturally + } else if (starCenters.length > 1) { + // Multiple stars - arrange in a circle + const centerX = width / 2; + const centerY = height / 2; + const radius = Math.min(width, height) * 0.3; + const angleStep = (2 * Math.PI) / starCenters.length; + + starCenters.forEach((center, i) => { + const angle = i * angleStep; + center.x = centerX + radius * Math.cos(angle); + center.y = centerY + radius * Math.sin(angle); + // Don't fix position initially - let simulation adjust + }); + } + + // Position content nodes around their star centers + starGroups.forEach((contentNodes, centerId) => { + const center = nodes.find(n => n.id === centerId); + if (!center) return; + + const angleStep = (2 * Math.PI) / Math.max(contentNodes.length, 1); + contentNodes.forEach((node, i) => { + const angle = i * angleStep; + node.x = (center.x || 0) + STAR_LINK_DISTANCE * Math.cos(angle); + node.y = (center.y || 0) + STAR_LINK_DISTANCE * Math.sin(angle); + }); + }); +} + +/** + * Handler for the start of a drag event in the star network simulation. + * Sets the fixed position of the node to its current position. + * @param event - The drag event from d3 + * @param d - The node being dragged + * @param simulation - The d3 force simulation instance + */ +function dragstarted(event: any, d: NetworkNode, simulation: Simulation) { + // If no other drag is active, set a low alpha target to keep the simulation running smoothly + if (!event.active) { + simulation.alphaTarget(0.1).restart(); + } + // Set the node's fixed position to its current position + d.fx = d.x; + d.fy = d.y; +} + +/** + * Handler for the drag event in the star network simulation. + * Updates the node's fixed position to follow the mouse. + * @param event - The drag event from d3 + * @param d - The node being dragged + */ +function dragged(event: any, d: NetworkNode) { + // Update the node's fixed position to the current mouse position + d.fx = event.x; + d.fy = event.y; +} + +/** + * Handler for the end of a drag event in the star network simulation. + * Keeps the node fixed at its new position after dragging. + * @param event - The drag event from d3 + * @param d - The node being dragged + * @param simulation - The d3 force simulation instance + */ +function dragended(event: any, d: NetworkNode, simulation: Simulation) { + // If no other drag is active, lower the alpha target to let the simulation cool down + if (!event.active) { + simulation.alphaTarget(0); + } + // Keep the node fixed at its new position + d.fx = event.x; + d.fy = event.y; +} + +/** + * Custom drag handler for star networks + * @param simulation - The d3 force simulation instance + * @returns The d3 drag behavior + */ +export function createStarDragHandler( + simulation: Simulation +): any { + // These handlers are now top-level functions, so we use closures to pass simulation to them. + // This is a common pattern in JavaScript/TypeScript when you need to pass extra arguments to event handlers. + return d3.drag() + .on('start', function(event: any, d: NetworkNode) { dragstarted(event, d, simulation); }) + .on('drag', dragged) + .on('end', function(event: any, d: NetworkNode) { dragended(event, d, simulation); }); +} \ No newline at end of file diff --git a/src/lib/navigator/EventNetwork/utils/starNetworkBuilder.ts b/src/lib/navigator/EventNetwork/utils/starNetworkBuilder.ts new file mode 100644 index 0000000..9f41031 --- /dev/null +++ b/src/lib/navigator/EventNetwork/utils/starNetworkBuilder.ts @@ -0,0 +1,353 @@ +/** + * Star Network Builder for NKBIP-01 Events + * + * This module provides utilities for building star network visualizations specifically + * for NKBIP-01 events (kinds 30040 and 30041). Unlike the sequential network builder, + * this creates star formations where index events (30040) are central nodes with + * content events (30041) arranged around them. + */ + +import type { NDKEvent } from "@nostr-dev-kit/ndk"; +import type { NetworkNode, NetworkLink, GraphData, GraphState } from "../types"; +import { getMatchingTags } from '$lib/utils/nostrUtils'; +import { createNetworkNode, createEventMap, extractEventIdFromATag, getEventColor } from './networkBuilder'; +import { createDebugFunction } from './common'; +import { wikiKind, indexKind, zettelKinds } from '$lib/consts'; + + +// Debug function +const debug = createDebugFunction("StarNetworkBuilder"); + +/** + * Represents a star network with a central index node and peripheral content nodes + */ +export interface StarNetwork { + center: NetworkNode; // Central index node (30040) + peripheralNodes: NetworkNode[]; // Content nodes (30041) and connected indices (30040) + links: NetworkLink[]; // Links within this star +} + +/** + * Creates a star network from an index event and its references + * + * @param indexEvent - The central index event (30040) + * @param state - Current graph state + * @param level - Hierarchy level for this star + * @returns A star network structure + */ +export function createStarNetwork( + indexEvent: NDKEvent, + state: GraphState, + level: number = 0 +): StarNetwork | null { + debug("Creating star network", { indexId: indexEvent.id, level }); + + const centerNode = state.nodeMap.get(indexEvent.id); + if (!centerNode) { + debug("Center node not found for index event", indexEvent.id); + return null; + } + + // Set the center node level + centerNode.level = level; + + // Extract referenced event IDs from 'a' tags + const referencedIds = getMatchingTags(indexEvent, "a") + .map(tag => extractEventIdFromATag(tag)) + .filter((id): id is string => id !== null); + + debug("Found referenced IDs", { count: referencedIds.length, ids: referencedIds }); + + // Get peripheral nodes (both content and nested indices) + const peripheralNodes: NetworkNode[] = []; + const links: NetworkLink[] = []; + + referencedIds.forEach(id => { + const node = state.nodeMap.get(id); + if (node) { + // Set the peripheral node level + node.level += 1; + peripheralNodes.push(node); + + // Create link from center to peripheral node + links.push({ + source: centerNode, + target: node, + isSequential: false // Star links are not sequential + }); + + debug("Added peripheral node", { nodeId: id, nodeType: node.type }); + } + }); + + return { + center: centerNode, + peripheralNodes, + links + }; +} + +/** + * Processes all index events to create star networks + * + * @param events - Array of all events + * @param maxLevel - Maximum nesting level to process + * @returns Array of star networks + */ +export function createStarNetworks( + events: NDKEvent[], + maxLevel: number, + existingNodeMap?: Map +): StarNetwork[] { + debug("Creating star networks", { eventCount: events.length, maxLevel }); + + // Use existing node map or create new one + const nodeMap = existingNodeMap || new Map(); + const eventMap = createEventMap(events); + + // Create nodes for all events if not using existing map + if (!existingNodeMap) { + events.forEach(event => { + if (!event.id) return; + const node = createNetworkNode(event); + nodeMap.set(event.id, node); + }); + } + + const state: GraphState = { + nodeMap, + links: [], + eventMap, + referencedIds: new Set() + }; + + // Find all index events and non-publication events + const publicationKinds = [wikiKind, indexKind, ...zettelKinds]; + const indexEvents = events.filter(event => event.kind === indexKind); + const nonPublicationEvents = events.filter(event => + event.kind !== undefined && !publicationKinds.includes(event.kind) + ); + + debug("Found index events", { count: indexEvents.length }); + debug("Found non-publication events", { count: nonPublicationEvents.length }); + + const starNetworks: StarNetwork[] = []; + const processedIndices = new Set(); + + // Process all index events regardless of level + indexEvents.forEach(indexEvent => { + if (!indexEvent.id || processedIndices.has(indexEvent.id)) return; + + const star = createStarNetwork(indexEvent, state, 0); + if (star && star.peripheralNodes.length > 0) { + starNetworks.push(star); + processedIndices.add(indexEvent.id); + debug("Created star network", { + centerId: star.center.id, + peripheralCount: star.peripheralNodes.length + }); + } + }); + + // Add non-publication events as standalone nodes (stars with no peripherals) + nonPublicationEvents.forEach(event => { + if (!event.id || !nodeMap.has(event.id)) return; + + const node = nodeMap.get(event.id)!; + const star: StarNetwork = { + center: node, + peripheralNodes: [], + links: [] + }; + starNetworks.push(star); + debug("Created standalone star for non-publication event", { + eventId: event.id, + kind: event.kind + }); + }); + + return starNetworks; +} + +/** + * Creates inter-star connections between star networks + * + * @param starNetworks - Array of star networks + * @returns Additional links connecting different star networks + */ +export function createInterStarConnections(starNetworks: StarNetwork[]): NetworkLink[] { + debug("Creating inter-star connections", { starCount: starNetworks.length }); + + const interStarLinks: NetworkLink[] = []; + + // Create a map of center nodes for quick lookup + const centerNodeMap = new Map(); + starNetworks.forEach(star => { + centerNodeMap.set(star.center.id, star.center); + }); + + // For each star, check if any of its peripheral nodes are centers of other stars + starNetworks.forEach(star => { + star.peripheralNodes.forEach(peripheralNode => { + // If this peripheral node is the center of another star, create an inter-star link + if (peripheralNode.isContainer && centerNodeMap.has(peripheralNode.id)) { + const targetStar = starNetworks.find(s => s.center.id === peripheralNode.id); + if (targetStar) { + interStarLinks.push({ + source: star.center, + target: targetStar.center, + isSequential: false + }); + debug("Created inter-star connection", { + from: star.center.id, + to: targetStar.center.id + }); + } + } + }); + }); + + return interStarLinks; +} + +/** + * Applies star-specific positioning to nodes using a radial layout + * + * @param starNetworks - Array of star networks + * @param width - Canvas width + * @param height - Canvas height + */ +export function applyStarLayout( + starNetworks: StarNetwork[], + width: number, + height: number +): void { + debug("Applying star layout", { + starCount: starNetworks.length, + dimensions: { width, height } + }); + + const centerX = width / 2; + const centerY = height / 2; + + // If only one star, center it + if (starNetworks.length === 1) { + const star = starNetworks[0]; + + // Position center node + star.center.x = centerX; + star.center.y = centerY; + star.center.fx = centerX; // Fix center position + star.center.fy = centerY; + + // Position peripheral nodes in a circle around center + const radius = Math.min(width, height) * 0.25; + const angleStep = (2 * Math.PI) / star.peripheralNodes.length; + + star.peripheralNodes.forEach((node, index) => { + const angle = index * angleStep; + node.x = centerX + radius * Math.cos(angle); + node.y = centerY + radius * Math.sin(angle); + }); + + return; + } + + // For multiple stars, arrange them in a grid or circle + const starsPerRow = Math.ceil(Math.sqrt(starNetworks.length)); + const starSpacingX = width / (starsPerRow + 1); + const starSpacingY = height / (Math.ceil(starNetworks.length / starsPerRow) + 1); + + starNetworks.forEach((star, index) => { + const row = Math.floor(index / starsPerRow); + const col = index % starsPerRow; + + const starCenterX = (col + 1) * starSpacingX; + const starCenterY = (row + 1) * starSpacingY; + + // Position center node + star.center.x = starCenterX; + star.center.y = starCenterY; + star.center.fx = starCenterX; // Fix center position + star.center.fy = starCenterY; + + // Position peripheral nodes around this star's center + const radius = Math.min(starSpacingX, starSpacingY) * 0.3; + const angleStep = (2 * Math.PI) / Math.max(star.peripheralNodes.length, 1); + + star.peripheralNodes.forEach((node, nodeIndex) => { + const angle = nodeIndex * angleStep; + node.x = starCenterX + radius * Math.cos(angle); + node.y = starCenterY + radius * Math.sin(angle); + }); + }); +} + +/** + * Generates a complete star network graph from events + * + * @param events - Array of Nostr events + * @param maxLevel - Maximum hierarchy level to process + * @returns Complete graph data with star network layout + */ +export function generateStarGraph( + events: NDKEvent[], + maxLevel: number +): GraphData { + debug("Generating star graph", { eventCount: events.length, maxLevel }); + + // Guard against empty events + if (!events || events.length === 0) { + return { nodes: [], links: [] }; + } + + // Initialize all nodes first + const nodeMap = new Map(); + events.forEach(event => { + if (!event.id) return; + const node = createNetworkNode(event); + nodeMap.set(event.id, node); + }); + + // Create star networks with the existing node map + const starNetworks = createStarNetworks(events, maxLevel, nodeMap); + + // Create inter-star connections + const interStarLinks = createInterStarConnections(starNetworks); + + // Collect nodes that are part of stars + const nodesInStars = new Set(); + const allLinks: NetworkLink[] = []; + + // Add nodes and links from all stars + starNetworks.forEach(star => { + nodesInStars.add(star.center.id); + star.peripheralNodes.forEach(node => { + nodesInStars.add(node.id); + }); + allLinks.push(...star.links); + }); + + // Add inter-star links + allLinks.push(...interStarLinks); + + // Include orphaned nodes (those not in any star) + const allNodes: NetworkNode[] = []; + nodeMap.forEach((node, id) => { + allNodes.push(node); + }); + + const result = { + nodes: allNodes, + links: allLinks + }; + + debug("Star graph generation complete", { + nodeCount: result.nodes.length, + linkCount: result.links.length, + starCount: starNetworks.length, + orphanedNodes: allNodes.length - nodesInStars.size + }); + + return result; +} \ No newline at end of file diff --git a/src/lib/navigator/EventNetwork/utils/tagNetworkBuilder.ts b/src/lib/navigator/EventNetwork/utils/tagNetworkBuilder.ts new file mode 100644 index 0000000..d4e28c4 --- /dev/null +++ b/src/lib/navigator/EventNetwork/utils/tagNetworkBuilder.ts @@ -0,0 +1,314 @@ +/** + * Tag Network Builder + * + * Enhances network visualizations with tag anchor nodes that act as gravity points + * for nodes sharing the same tags. + */ + +import type { NDKEvent } from "@nostr-dev-kit/ndk"; +import type { NetworkNode, NetworkLink, GraphData } from "../types"; +import { getDisplayNameSync } from "$lib/utils/profileCache"; +import { SeededRandom, createDebugFunction } from "./common"; + +// Configuration +const TAG_ANCHOR_RADIUS = 15; +// TODO: Move this to settings panel for user control +const TAG_ANCHOR_PLACEMENT_RADIUS = 1250; // Radius from center within which to randomly place tag anchors + +// Debug function +const debug = createDebugFunction("TagNetworkBuilder"); + + +/** + * Creates a deterministic seed from a string + */ +function createSeed(str: string): number { + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; // Convert to 32-bit integer + } + return Math.abs(hash); +} + +/** + * Color mapping for tag anchor nodes + */ +export function getTagAnchorColor(tagType: string): string { + switch (tagType) { + case "t": + return "#eba5a5"; // Blue for hashtags + case "p": + return "#10B981"; // Green for people + case "author": + return "#8B5CF6"; // Purple for authors + case "e": + return "#F59E0B"; // Yellow for events + case "a": + return "#EF4444"; // Red for articles + case "kind3": + return "#06B6D4"; // Cyan for follow lists + default: + return "#6B7280"; // Gray for others + } +} + +/** + * Extracts unique tags from events for a specific tag type + */ +export function extractUniqueTagsForType( + events: NDKEvent[], + tagType: string, +): Map> { + // Map of tagValue -> Set of event IDs + const tagMap = new Map>(); + debug("Extracting unique tags for type", { tagType, eventCount: events.length }); + + events.forEach((event) => { + if (!event.tags || !event.id) return; + + event.tags.forEach((tag) => { + if (tag.length < 2) return; + + if (tag[0] !== tagType) return; + const tagValue = tag[1]; + + if (!tagValue) return; + + if (!tagMap.has(tagValue)) { + tagMap.set(tagValue, new Set()); + } + + tagMap.get(tagValue)!.add(event.id); + }); + }); + + debug("Extracted tags", { tagCount: tagMap.size }); + + return tagMap; +} + +/** + * Creates tag anchor nodes from extracted tags of a specific type + */ +export function createTagAnchorNodes( + tagMap: Map>, + tagType: string, + width: number, + height: number, +): NetworkNode[] { + const anchorNodes: NetworkNode[] = []; + + debug("Creating tag anchor nodes", { tagType, tagCount: tagMap.size }); + + // Calculate positions for tag anchors randomly within radius + // Show all tags regardless of how many events they appear in + const minEventCount = 1; + let validTags = Array.from(tagMap.entries()).filter( + ([_, eventIds]) => eventIds.size >= minEventCount, + ); + + if (validTags.length === 0) return []; + + // Sort all tags by number of connections (events) descending + validTags.sort((a, b) => b[1].size - a[1].size); + + validTags.forEach(([tagValue, eventIds]) => { + // Position anchors randomly within a radius from the center + const centerX = width / 2; + const centerY = height / 2; + + // Create seeded random generator based on tag type and value for consistent positioning + const seedString = `${tagType}-${tagValue}`; + const rng = new SeededRandom(createSeed(seedString)); + + // Generate deterministic position within the defined radius + const angle = rng.next() * 2 * Math.PI; + const distance = rng.next() * TAG_ANCHOR_PLACEMENT_RADIUS; + const x = centerX + distance * Math.cos(angle); + const y = centerY + distance * Math.sin(angle); + + // Format the display title based on tag type + let displayTitle = tagValue; + if (tagType === "t") { + displayTitle = tagValue.startsWith("#") ? tagValue : `#${tagValue}`; + } else if (tagType === "author") { + displayTitle = tagValue; + } else if (tagType === "p") { + // Use display name for pubkey + displayTitle = getDisplayNameSync(tagValue); + } + + const anchorNode: NetworkNode = { + id: `tag-anchor-${tagType}-${tagValue}`, + title: displayTitle, + content: `${eventIds.size} events`, + author: "", + kind: 0, // Special kind for tag anchors + type: "TagAnchor", + level: -1, // Tag anchors are outside the hierarchy + isTagAnchor: true, + tagType, + tagValue, + connectedNodes: Array.from(eventIds), + x, + y, + fx: x, // Fix position + fy: y, + }; + + anchorNodes.push(anchorNode); + }); + + debug("Created tag anchor nodes", { count: anchorNodes.length }); + return anchorNodes; +} + +/** + * Creates invisible links between tag anchors and nodes that have those tags + */ +export function createTagLinks( + tagAnchors: NetworkNode[], + nodes: NetworkNode[], +): NetworkLink[] { + debug("Creating tag links", { anchorCount: tagAnchors.length, nodeCount: nodes.length }); + + const links: NetworkLink[] = []; + const nodeMap = new Map(nodes.map((n) => [n.id, n])); + + tagAnchors.forEach((anchor) => { + if (!anchor.connectedNodes) return; + + anchor.connectedNodes.forEach((nodeId) => { + const node = nodeMap.get(nodeId); + if (node) { + links.push({ + source: anchor, + target: node, + isSequential: false, + }); + } + }); + }); + + debug("Created tag links", { linkCount: links.length }); + return links; +} + +/** + * Enhances a graph with tag anchor nodes for a specific tag type + */ +export function enhanceGraphWithTags( + graphData: GraphData, + events: NDKEvent[], + tagType: string, + width: number, + height: number, + displayLimit?: number, +): GraphData { + debug("Enhancing graph with tags", { tagType, displayLimit }); + + // Extract unique tags for the specified type + const tagMap = extractUniqueTagsForType(events, tagType); + + // Create tag anchor nodes + let tagAnchors = createTagAnchorNodes(tagMap, tagType, width, height); + + // Apply display limit if provided + if (displayLimit && displayLimit > 0 && tagAnchors.length > displayLimit) { + // Sort by connection count (already done in createTagAnchorNodes) + // and take only the top ones up to the limit + tagAnchors = tagAnchors.slice(0, displayLimit); + } + + // Create links between anchors and nodes + const tagLinks = createTagLinks(tagAnchors, graphData.nodes); + + // Return enhanced graph + return { + nodes: [...graphData.nodes, ...tagAnchors], + links: [...graphData.links, ...tagLinks], + }; +} + +/** + * Applies a gentle pull on each node toward its tag anchors. + * + * @param nodes - The array of network nodes to update. + * @param nodeToAnchors - A map from node IDs to their tag anchor nodes. + * @param alpha - The current simulation alpha (cooling factor). + */ +export function applyTagGravity( + nodes: NetworkNode[], + nodeToAnchors: Map, + alpha: number +): void { + nodes.forEach((node) => { + if (node.isTagAnchor) return; // Tag anchors don't move + + const anchors = nodeToAnchors.get(node.id); + if (!anchors || anchors.length === 0) return; + + // Apply gentle pull toward each tag anchor + anchors.forEach((anchor) => { + if ( + anchor.x != null && + anchor.y != null && + node.x != null && + node.y != null + ) { + const dx = anchor.x - node.x; + const dy = anchor.y - node.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance > 0) { + // Gentle force that decreases with distance + const strength = (0.02 * alpha) / anchors.length; + node.vx = (node.vx || 0) + (dx / distance) * strength * distance; + node.vy = (node.vy || 0) + (dy / distance) * strength * distance; + } + } + }); + }); +} + +/** + * Custom force for tag anchor gravity + */ +export function createTagGravityForce( + nodes: NetworkNode[], + links: NetworkLink[], +): any { + // Build a map of nodes to their tag anchors + const nodeToAnchors = new Map(); + + links.forEach((link) => { + const source = link.source as NetworkNode; + const target = link.target as NetworkNode; + + if (source.isTagAnchor && !target.isTagAnchor) { + if (!nodeToAnchors.has(target.id)) { + nodeToAnchors.set(target.id, []); + } + nodeToAnchors.get(target.id)!.push(source); + } else if (target.isTagAnchor && !source.isTagAnchor) { + if (!nodeToAnchors.has(source.id)) { + nodeToAnchors.set(source.id, []); + } + nodeToAnchors.get(source.id)!.push(target); + } + }); + + debug("Creating tag gravity force"); + + function force(alpha: number) { + applyTagGravity(nodes, nodeToAnchors, alpha); + } + + force.initialize = function (_: NetworkNode[]) { + nodes = _; + }; + + return force; +} diff --git a/src/lib/ndk.ts b/src/lib/ndk.ts index 17dbf69..70592ba 100644 --- a/src/lib/ndk.ts +++ b/src/lib/ndk.ts @@ -6,7 +6,7 @@ import NDK, { NDKUser, NDKEvent, } from "@nostr-dev-kit/ndk"; -import { get, writable, type Writable } from "svelte/store"; +import { writable, get, type Writable } from "svelte/store"; import { loginStorageKey, } from "./consts.ts"; @@ -33,6 +33,11 @@ export const outboxRelays = writable([]); export const activeInboxRelays = writable([]); export const activeOutboxRelays = writable([]); +// Subscribe to userStore changes and update ndkSignedIn accordingly +userStore.subscribe((userState) => { + ndkSignedIn.set(userState.signedIn); +}); + /** * Custom authentication policy that handles NIP-42 authentication manually * when the default NDK authentication fails diff --git a/src/lib/state.ts b/src/lib/state.ts index 280fb48..ba4f8b4 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -11,5 +11,5 @@ export const tabBehaviour: Writable = writable( export const userPublickey: Writable = writable( (browser && localStorage.getItem("wikinostr_loggedInPublicKey")) || "", ); -export const networkFetchLimit: Writable = writable(5); +export const networkFetchLimit: Writable = writable(50); export const levelsToRender: Writable = writable(3); diff --git a/src/lib/stores/visualizationConfig.ts b/src/lib/stores/visualizationConfig.ts new file mode 100644 index 0000000..a17c052 --- /dev/null +++ b/src/lib/stores/visualizationConfig.ts @@ -0,0 +1,182 @@ +import { writable, derived, get } from "svelte/store"; + +export interface EventKindConfig { + kind: number; + limit: number; + enabled?: boolean; // Whether this kind is enabled for display + nestedLevels?: number; // Only for kind 30040 + depth?: number; // Only for kind 3 (follow lists) + showAll?: boolean; // Only for content kinds (30041, 30818) - show all loaded content instead of limit +} + +/** + * VisualizationConfig now uses a Map for eventConfigs. + * The key is the event kind (number), and the value is a JSON stringified EventKindConfig. + * This allows O(1) retrieval of config by kind. + */ +export interface VisualizationConfig { + /** + * Event configurations with per-kind limits. + */ + eventConfigs: EventKindConfig[]; + + /** + * Whether to search through all fetched events during graph traversal. + */ + searchThroughFetched: boolean; +} + +// Default configurations for common event kinds +const DEFAULT_EVENT_CONFIGS: EventKindConfig[] = [ + { kind: 30040, limit: 20, nestedLevels: 1, enabled: true }, + { kind: 30041, limit: 20, enabled: false }, + { kind: 30818, limit: 20, enabled: false }, + { kind: 30023, limit: 20, enabled: false }, +]; + +function createVisualizationConfig() { + const initialConfig: VisualizationConfig = { + eventConfigs: DEFAULT_EVENT_CONFIGS, + searchThroughFetched: true, + }; + + const { subscribe, set, update } = writable(initialConfig); + + function reset() { + set(initialConfig); + } + + function addEventKind(kind: number, limit: number = 10) { + update((config) => { + // Check if kind already exists + if (config.eventConfigs.some((ec) => ec.kind === kind)) { + return config; + } + + const newConfig: EventKindConfig = { kind, limit, enabled: true }; + + // Add nestedLevels for 30040 + if (kind === 30040) { + newConfig.nestedLevels = 1; + } + + // Add depth for kind 3 + if (kind === 3) { + newConfig.depth = 0; + } + + return { + ...config, + eventConfigs: [...config.eventConfigs, newConfig], + }; + }); + } + + function removeEventKind(kind: number) { + update((config) => ({ + ...config, + eventConfigs: config.eventConfigs.filter((ec) => ec.kind !== kind), + })); + } + + function updateEventLimit(kind: number, limit: number) { + update((config) => ({ + ...config, + eventConfigs: config.eventConfigs.map((ec) => + ec.kind === kind ? { ...ec, limit } : ec, + ), + })); + } + + function updateNestedLevels(levels: number) { + update((config) => ({ + ...config, + eventConfigs: config.eventConfigs.map((ec) => + ec.kind === 30040 ? { ...ec, nestedLevels: levels } : ec, + ), + })); + } + + function updateFollowDepth(depth: number) { + update((config) => ({ + ...config, + eventConfigs: config.eventConfigs.map((ec) => + ec.kind === 3 ? { ...ec, depth: depth } : ec, + ), + })); + } + + function toggleShowAllContent(kind: number) { + update((config) => ({ + ...config, + eventConfigs: config.eventConfigs.map((ec) => + ec.kind === kind ? { ...ec, showAll: !ec.showAll } : ec, + ), + })); + } + + function getEventConfig(kind: number) { + let config: EventKindConfig | undefined; + subscribe((c) => { + config = c.eventConfigs.find((ec) => ec.kind === kind); + })(); + return config; + } + + function toggleSearchThroughFetched() { + update((config) => ({ + ...config, + searchThroughFetched: !config.searchThroughFetched, + })); + } + + function toggleKind(kind: number) { + update((config) => ({ + ...config, + eventConfigs: config.eventConfigs.map((ec) => + ec.kind === kind ? { ...ec, enabled: !ec.enabled } : ec, + ), + })); + } + + return { + subscribe, + update, + reset, + addEventKind, + removeEventKind, + updateEventLimit, + updateNestedLevels, + updateFollowDepth, + toggleShowAllContent, + getEventConfig, + toggleSearchThroughFetched, + toggleKind, + }; +} + +export const visualizationConfig = createVisualizationConfig(); + +// Helper to get all enabled event kinds +export const enabledEventKinds = derived(visualizationConfig, ($config) => + $config.eventConfigs + .filter((ec) => ec.enabled !== false) + .map((ec) => ec.kind), +); + +/** + * Returns true if the given event kind is enabled in the config. + * @param config - The VisualizationConfig object. + * @param kind - The event kind number to check. + */ +export function isKindEnabledFn(config: VisualizationConfig, kind: number): boolean { + const eventConfig = config.eventConfigs.find((ec) => ec.kind === kind); + // If not found, return false. Otherwise, return true unless explicitly disabled. + return !!eventConfig && eventConfig.enabled !== false; +} + +// Derived store: returns a function that checks if a kind is enabled in the current config. +export const isKindEnabledStore = derived( + visualizationConfig, + ($config) => (kind: number) => isKindEnabledFn($config, kind) +); diff --git a/src/lib/types.ts b/src/lib/types.ts index 06e130c..9587b0c 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -13,3 +13,21 @@ export type TabType = | "user" | "settings" | "editor"; + +export type EventCounts = { [kind: number]: number }; + +/** + * Enum of Nostr event kinds relevant to Alexandria. + */ +export enum NostrKind { + /** User metadata event (kind 0) */ + UserMetadata = 0, + /** Text note event (kind 1) */ + TextNote = 1, + /** Publication index event (kind 30040) */ + PublicationIndex = 30040, + /** Publication content event (kind 30041) */ + PublicationContent = 30041, + /** Wiki event (kind 30818) */ + Wiki = 30818, +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c59d574..ee44929 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,6 +1,22 @@ import type { NDKEvent } from "@nostr-dev-kit/ndk"; import { nip19 } from "nostr-tools"; import { getMatchingTags } from "./utils/nostrUtils.ts"; +import type { AddressPointer, EventPointer } from "nostr-tools/nip19"; +import type { NostrEvent } from "./utils/websocket_utils.ts"; + +export class DecodeError extends Error { + constructor(message: string) { + super(message); + this.name = "DecodeError"; + } +} + +export class InvalidKindError extends DecodeError { + constructor(message: string) { + super(message); + this.name = "InvalidKindError"; + } +} export function neventEncode(event: NDKEvent, relays: string[]) { return nip19.neventEncode({ @@ -25,10 +41,68 @@ export function naddrEncode(event: NDKEvent, relays: string[]) { }); } +/** + * Creates a tag address from a raw Nostr event (for compatibility with NDK events) + * @param event The raw Nostr event + * @param relays Optional relay list for the address + * @returns A tag address string + */ +export function createTagAddress(event: NostrEvent, relays: string[] = []): string { + const dTag = event.tags.find((tag: string[]) => tag[0] === "d")?.[1]; + if (!dTag) { + throw new Error("Event does not have a d tag"); + } + + return nip19.naddrEncode({ + identifier: dTag, + pubkey: event.pubkey, + kind: event.kind, + relays, + }); +} + export function nprofileEncode(pubkey: string, relays: string[]) { return nip19.nprofileEncode({ pubkey, relays }); } +/** + * Decodes a nostr identifier (naddr, nevent) and returns the decoded data. + * @param identifier The nostr identifier to decode. + * @param expectedType The expected type of the decoded data ('naddr' or 'nevent'). + * @returns The decoded data. + */ +function decodeNostrIdentifier( + identifier: string, + expectedType: "naddr" | "nevent", +): T { + try { + if (!identifier.startsWith(expectedType)) { + throw new InvalidKindError(`Invalid ${expectedType} format`); + } + const decoded = nip19.decode(identifier); + if (decoded.type !== expectedType) { + throw new InvalidKindError(`Decoded result is not an ${expectedType}`); + } + return decoded.data as T; + } catch (error) { + throw new DecodeError(`Failed to decode ${expectedType}: ${error}`); + } +} + +/** + * Decodes an naddr identifier and returns the decoded data + */ +export function naddrDecode(naddr: string): AddressPointer { + return decodeNostrIdentifier(naddr, "naddr"); +} + +/** + * Decodes an nevent identifier and returns the decoded data + */ +export function neventDecode(nevent: string): EventPointer { + return decodeNostrIdentifier(nevent, "nevent"); +} + export function formatDate(unixtimestamp: number) { const months = [ "Jan", @@ -169,7 +243,8 @@ Array.prototype.findIndexAsync = function ( * @param wait The number of milliseconds to delay * @returns A debounced version of the function */ -export function debounce unknown>( +// deno-lint-ignore no-explicit-any +export function debounce any>( func: T, wait: number, ): (...args: Parameters) => void { diff --git a/src/lib/utils/displayLimits.ts b/src/lib/utils/displayLimits.ts new file mode 100644 index 0000000..029ec25 --- /dev/null +++ b/src/lib/utils/displayLimits.ts @@ -0,0 +1,142 @@ +import type { NDKEvent } from '@nostr-dev-kit/ndk'; +import type { VisualizationConfig } from '$lib/stores/visualizationConfig'; +import { isEventId, isCoordinate, parseCoordinate } from './nostr_identifiers'; +import type { NostrEventId } from './nostr_identifiers'; + +/** + * Filters events based on visualization configuration + * @param events - All available events + * @param config - Visualization configuration + * @returns Filtered events that should be displayed + */ +export function filterByDisplayLimits(events: NDKEvent[], config: VisualizationConfig): NDKEvent[] { + const result: NDKEvent[] = []; + const kindCounts = new Map(); + + for (const event of events) { + const kind = event.kind; + if (kind === undefined) continue; + + // Get the config for this event kind + const eventConfig = config.eventConfigs.find(ec => ec.kind === kind); + + // Skip if the kind is disabled + if (eventConfig && eventConfig.enabled === false) { + continue; + } + + const limit = eventConfig?.limit; + + // Special handling for content kinds (30041, 30818) with showAll option + if ((kind === 30041 || kind === 30818) && eventConfig?.showAll) { + // Show all content events when showAll is true + result.push(event); + // Still update the count for UI display + const currentCount = kindCounts.get(kind) || 0; + kindCounts.set(kind, currentCount + 1); + } else if (limit !== undefined) { + // Normal limit checking + const currentCount = kindCounts.get(kind) || 0; + if (currentCount < limit) { + result.push(event); + kindCounts.set(kind, currentCount + 1); + } + } else { + // No limit configured, add the event + result.push(event); + } + } + + return result; +} + +/** + * Detects events that are referenced but not present in the current set + * @param events - Current events + * @param existingIds - Set of all known event IDs (hex format) + * @param existingCoordinates - Optional map of existing coordinates for NIP-33 detection + * @returns Set of missing event identifiers + */ +export function detectMissingEvents( + events: NDKEvent[], + existingIds: Set, + existingCoordinates?: Map +): Set { + const missing = new Set(); + + for (const event of events) { + // Check 'e' tags for direct event references (hex IDs) + const eTags = event.getMatchingTags('e'); + for (const eTag of eTags) { + if (eTag.length < 2) continue; + + const eventId = eTag[1]; + + // Type check: ensure it's a valid hex event ID + if (!isEventId(eventId)) { + console.warn('Invalid event ID in e tag:', eventId); + continue; + } + + if (!existingIds.has(eventId)) { + missing.add(eventId); + } + } + + // Check 'a' tags for NIP-33 references (kind:pubkey:d-tag) + const aTags = event.getMatchingTags('a'); + for (const aTag of aTags) { + if (aTag.length < 2) continue; + + const identifier = aTag[1]; + + // Type check: ensure it's a valid coordinate + if (!isCoordinate(identifier)) { + console.warn('Invalid coordinate in a tag:', identifier); + continue; + } + + // Parse the coordinate + const parsed = parseCoordinate(identifier); + if (!parsed) continue; + + // If we have existing coordinates, check if this one exists + if (existingCoordinates) { + if (!existingCoordinates.has(identifier)) { + missing.add(identifier); + } + } else { + // Without coordinate map, we can't detect missing NIP-33 events + // This is a limitation when we only have hex IDs + console.debug('Cannot detect missing NIP-33 events without coordinate map:', identifier); + } + } + } + + return missing; +} + +/** + * Builds a map of coordinates to events for NIP-33 detection + * @param events - Array of events to build coordinate map from + * @returns Map of coordinate strings to events + */ +export function buildCoordinateMap(events: NDKEvent[]): Map { + const coordinateMap = new Map(); + + for (const event of events) { + // Only process replaceable events (kinds 30000-39999) + if (event.kind && event.kind >= 30000 && event.kind < 40000) { + const dTag = event.tagValue('d'); + const author = event.pubkey; + + if (dTag && author) { + const coordinate = `${event.kind}:${author}:${dTag}`; + coordinateMap.set(coordinate, event); + } + } + } + + return coordinateMap; +} + diff --git a/src/lib/utils/eventColors.ts b/src/lib/utils/eventColors.ts new file mode 100644 index 0000000..e123c7b --- /dev/null +++ b/src/lib/utils/eventColors.ts @@ -0,0 +1,82 @@ +/** + * Deterministic color mapping for event kinds + * Uses golden ratio to distribute colors evenly across the spectrum + */ + +const GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2; + +/** + * Get a deterministic color for an event kind + * @param kind - The event kind number + * @returns HSL color string + */ +export function getEventKindColor(kind: number): string { + // Use golden ratio for better distribution + const hue = (kind * GOLDEN_RATIO * 360) % 360; + + // Use different saturation/lightness for better visibility + const saturation = 65 + (kind % 20); // 65-85% + const lightness = 55 + ((kind * 3) % 15); // 55-70% + + return `hsl(${Math.round(hue)}, ${saturation}%, ${lightness}%)`; +} + +/** + * Get a friendly name for an event kind + * @param kind - The event kind number + * @returns Human-readable name + */ +export function getEventKindName(kind: number): string { + const kindNames: Record = { + 0: 'Metadata', + 1: 'Text Note', + 2: 'Recommend Relay', + 3: 'Contact List', + 4: 'Encrypted DM', + 5: 'Event Deletion', + 6: 'Repost', + 7: 'Reaction', + 8: 'Badge Award', + 16: 'Generic Repost', + 40: 'Channel Creation', + 41: 'Channel Metadata', + 42: 'Channel Message', + 43: 'Channel Hide Message', + 44: 'Channel Mute User', + 1984: 'Reporting', + 9734: 'Zap Request', + 9735: 'Zap', + 10000: 'Mute List', + 10001: 'Pin List', + 10002: 'Relay List', + 22242: 'Client Authentication', + 24133: 'Nostr Connect', + 27235: 'HTTP Auth', + 30000: 'Categorized People List', + 30001: 'Categorized Bookmark List', + 30008: 'Profile Badges', + 30009: 'Badge Definition', + 30017: 'Create or update a stall', + 30018: 'Create or update a product', + 30023: 'Long-form Content', + 30024: 'Draft Long-form Content', + 30040: 'Publication Index', + 30041: 'Publication Content', + 30078: 'Application-specific Data', + 30311: 'Live Event', + 30402: 'Classified Listing', + 30403: 'Draft Classified Listing', + 30617: 'Repository', + 30818: 'Wiki Page', + 31922: 'Date-Based Calendar Event', + 31923: 'Time-Based Calendar Event', + 31924: 'Calendar', + 31925: 'Calendar Event RSVP', + 31989: 'Handler recommendation', + 31990: 'Handler information', + 34550: 'Community Definition', + }; + + return kindNames[kind] || `Kind ${kind}`; +} + diff --git a/src/lib/utils/eventDeduplication.ts b/src/lib/utils/eventDeduplication.ts new file mode 100644 index 0000000..8c52e64 --- /dev/null +++ b/src/lib/utils/eventDeduplication.ts @@ -0,0 +1,214 @@ +import type { NDKEvent } from '@nostr-dev-kit/ndk'; + +/** + * Deduplicate content events by keeping only the most recent version + * @param contentEventSets Array of event sets from different sources + * @returns Map of coordinate to most recent event + */ +export function deduplicateContentEvents(contentEventSets: Set[]): Map { + const eventsByCoordinate = new Map(); + + // Track statistics for debugging + let totalEvents = 0; + let duplicateCoordinates = 0; + const duplicateDetails: Array<{ coordinate: string; count: number; events: string[] }> = []; + + contentEventSets.forEach((eventSet) => { + eventSet.forEach(event => { + totalEvents++; + const dTag = event.tagValue("d"); + const author = event.pubkey; + const kind = event.kind; + + if (dTag && author && kind) { + const coordinate = `${kind}:${author}:${dTag}`; + const existing = eventsByCoordinate.get(coordinate); + + if (existing) { + // We found a duplicate coordinate + duplicateCoordinates++; + + // Track details for the first few duplicates + if (duplicateDetails.length < 5) { + const existingDetails = duplicateDetails.find(d => d.coordinate === coordinate); + if (existingDetails) { + existingDetails.count++; + existingDetails.events.push(`${event.id} (created_at: ${event.created_at})`); + } else { + duplicateDetails.push({ + coordinate, + count: 2, // existing + current + events: [ + `${existing.id} (created_at: ${existing.created_at})`, + `${event.id} (created_at: ${event.created_at})` + ] + }); + } + } + } + + // Keep the most recent event (highest created_at) + if (!existing || (event.created_at !== undefined && existing.created_at !== undefined && event.created_at > existing.created_at)) { + eventsByCoordinate.set(coordinate, event); + } + } + }); + }); + + // Log deduplication results if any duplicates were found + if (duplicateCoordinates > 0) { + console.log(`[eventDeduplication] Found ${duplicateCoordinates} duplicate events out of ${totalEvents} total events`); + console.log(`[eventDeduplication] Reduced to ${eventsByCoordinate.size} unique coordinates`); + console.log(`[eventDeduplication] Duplicate details:`, duplicateDetails); + } else if (totalEvents > 0) { + console.log(`[eventDeduplication] No duplicates found in ${totalEvents} events`); + } + + return eventsByCoordinate; +} + +/** + * Deduplicate and combine all events, keeping only the most recent version of replaceable events + * @param nonPublicationEvents Array of non-publication events + * @param validIndexEvents Set of valid index events + * @param contentEvents Set of content events + * @returns Array of deduplicated events + */ +export function deduplicateAndCombineEvents( + nonPublicationEvents: NDKEvent[], + validIndexEvents: Set, + contentEvents: Set +): NDKEvent[] { + // Track statistics for debugging + const initialCount = nonPublicationEvents.length + validIndexEvents.size + contentEvents.size; + let replaceableEventsProcessed = 0; + let duplicateCoordinatesFound = 0; + const duplicateDetails: Array<{ coordinate: string; count: number; events: string[] }> = []; + + // First, build coordinate map for replaceable events + const coordinateMap = new Map(); + const allEventsToProcess = [ + ...nonPublicationEvents, // Non-publication events fetched earlier + ...Array.from(validIndexEvents), + ...Array.from(contentEvents) + ]; + + // First pass: identify the most recent version of each replaceable event + allEventsToProcess.forEach(event => { + if (!event.id) return; + + // For replaceable events (30000-39999), track by coordinate + if (event.kind && event.kind >= 30000 && event.kind < 40000) { + replaceableEventsProcessed++; + const dTag = event.tagValue("d"); + const author = event.pubkey; + + if (dTag && author) { + const coordinate = `${event.kind}:${author}:${dTag}`; + const existing = coordinateMap.get(coordinate); + + if (existing) { + // We found a duplicate coordinate + duplicateCoordinatesFound++; + + // Track details for the first few duplicates + if (duplicateDetails.length < 5) { + const existingDetails = duplicateDetails.find(d => d.coordinate === coordinate); + if (existingDetails) { + existingDetails.count++; + existingDetails.events.push(`${event.id} (created_at: ${event.created_at})`); + } else { + duplicateDetails.push({ + coordinate, + count: 2, // existing + current + events: [ + `${existing.id} (created_at: ${existing.created_at})`, + `${event.id} (created_at: ${event.created_at})` + ] + }); + } + } + } + + // Keep the most recent version + if (!existing || (event.created_at !== undefined && existing.created_at !== undefined && event.created_at > existing.created_at)) { + coordinateMap.set(coordinate, event); + } + } + } + }); + + // Second pass: build final event map + const finalEventMap = new Map(); + const seenCoordinates = new Set(); + + allEventsToProcess.forEach(event => { + if (!event.id) return; + + // For replaceable events, only add if it's the chosen version + if (event.kind && event.kind >= 30000 && event.kind < 40000) { + const dTag = event.tagValue("d"); + const author = event.pubkey; + + if (dTag && author) { + const coordinate = `${event.kind}:${author}:${dTag}`; + const chosenEvent = coordinateMap.get(coordinate); + + // Only add this event if it's the chosen one for this coordinate + if (chosenEvent && chosenEvent.id === event.id) { + if (!seenCoordinates.has(coordinate)) { + finalEventMap.set(event.id, event); + seenCoordinates.add(coordinate); + } + } + return; + } + } + + // Non-replaceable events are added directly + finalEventMap.set(event.id, event); + }); + + const finalCount = finalEventMap.size; + const reduction = initialCount - finalCount; + + // Log deduplication results if any duplicates were found + if (duplicateCoordinatesFound > 0) { + console.log(`[eventDeduplication] deduplicateAndCombineEvents: Found ${duplicateCoordinatesFound} duplicate coordinates out of ${replaceableEventsProcessed} replaceable events`); + console.log(`[eventDeduplication] deduplicateAndCombineEvents: Reduced from ${initialCount} to ${finalCount} events (${reduction} removed)`); + console.log(`[eventDeduplication] deduplicateAndCombineEvents: Duplicate details:`, duplicateDetails); + } else if (replaceableEventsProcessed > 0) { + console.log(`[eventDeduplication] deduplicateAndCombineEvents: No duplicates found in ${replaceableEventsProcessed} replaceable events`); + } + + return Array.from(finalEventMap.values()); +} + +/** + * Check if an event is a replaceable event (kinds 30000-39999) + * @param event The event to check + * @returns True if the event is replaceable + */ +export function isReplaceableEvent(event: NDKEvent): boolean { + return event.kind !== undefined && event.kind >= 30000 && event.kind < 40000; +} + +/** + * Get the coordinate for a replaceable event + * @param event The event to get the coordinate for + * @returns The coordinate string (kind:pubkey:d-tag) or null if not a valid replaceable event + */ +export function getEventCoordinate(event: NDKEvent): string | null { + if (!isReplaceableEvent(event)) { + return null; + } + + const dTag = event.tagValue("d"); + const author = event.pubkey; + + if (!dTag || !author) { + return null; + } + + return `${event.kind}:${author}:${dTag}`; +} \ No newline at end of file diff --git a/src/lib/utils/event_kind_utils.ts b/src/lib/utils/event_kind_utils.ts new file mode 100644 index 0000000..7d40715 --- /dev/null +++ b/src/lib/utils/event_kind_utils.ts @@ -0,0 +1,98 @@ +import type { EventKindConfig } from '$lib/stores/visualizationConfig'; + +/** + * Validates an event kind input value. + * @param value - The input value to validate (string or number). + * @param existingKinds - Array of existing event kind numbers to check for duplicates. + * @returns The validated kind number, or null if validation fails. + */ +export function validateEventKind( + value: string | number, + existingKinds: number[] +): { kind: number | null; error: string } { + // Convert to string for consistent handling + const strValue = String(value); + if (strValue === null || strValue === undefined || strValue.trim() === '') { + return { kind: null, error: '' }; + } + + const kind = parseInt(strValue.trim()); + if (isNaN(kind)) { + return { kind: null, error: 'Must be a number' }; + } + + if (kind < 0) { + return { kind: null, error: 'Must be non-negative' }; + } + + if (existingKinds.includes(kind)) { + return { kind: null, error: 'Already added' }; + } + + return { kind, error: '' }; +} + +/** + * Handles adding a new event kind with validation and state management. + * @param newKind - The new kind value to add. + * @param existingKinds - Array of existing event kind numbers. + * @param addKindFunction - Function to call when adding the kind. + * @param resetStateFunction - Function to call to reset the input state. + * @returns Object with success status and any error message. + */ +export function handleAddEventKind( + newKind: string, + existingKinds: number[], + addKindFunction: (kind: number) => void, + resetStateFunction: () => void +): { success: boolean; error: string } { + console.log('[handleAddEventKind] called with:', newKind); + + const validation = validateEventKind(newKind, existingKinds); + console.log('[handleAddEventKind] Validation result:', validation); + + if (validation.kind !== null) { + console.log('[handleAddEventKind] Adding event kind:', validation.kind); + addKindFunction(validation.kind); + resetStateFunction(); + return { success: true, error: '' }; + } else { + console.log('[handleAddEventKind] Validation failed:', validation.error); + return { success: false, error: validation.error }; + } +} + +/** + * Handles keyboard events for event kind input. + * @param e - The keyboard event. + * @param onEnter - Function to call when Enter is pressed. + * @param onEscape - Function to call when Escape is pressed. + */ +export function handleEventKindKeydown( + e: KeyboardEvent, + onEnter: () => void, + onEscape: () => void +): void { + if (e.key === 'Enter') { + onEnter(); + } else if (e.key === 'Escape') { + onEscape(); + } +} + +/** + * Gets the display name for an event kind. + * @param kind - The event kind number. + * @returns The display name for the kind. + */ +export function getEventKindDisplayName(kind: number): string { + switch (kind) { + case 30040: return 'Publication Index'; + case 30041: return 'Publication Content'; + case 30818: return 'Wiki'; + case 1: return 'Text Note'; + case 0: return 'Metadata'; + case 3: return 'Follow List'; + default: return `Kind ${kind}`; + } +} \ No newline at end of file diff --git a/src/lib/utils/markup/advancedAsciidoctorPostProcessor.ts b/src/lib/utils/markup/advancedAsciidoctorPostProcessor.ts index 7106c58..41e4df9 100644 --- a/src/lib/utils/markup/advancedAsciidoctorPostProcessor.ts +++ b/src/lib/utils/markup/advancedAsciidoctorPostProcessor.ts @@ -32,9 +32,11 @@ export async function postProcessAdvancedAsciidoctorHtml( } if ( typeof globalThis !== "undefined" && - typeof (globalThis.MathJax as any)?.typesetPromise === "function" + // deno-lint-ignore no-explicit-any + typeof (globalThis as any).MathJax?.typesetPromise === "function" ) { - setTimeout(() => (globalThis.MathJax as any).typesetPromise(), 0); + // deno-lint-ignore no-explicit-any + setTimeout(() => (globalThis as any).MathJax.typesetPromise(), 0); } return processedHtml; } catch (error) { diff --git a/src/lib/utils/markup/asciidoctorPostProcessor.ts b/src/lib/utils/markup/asciidoctorPostProcessor.ts index dcfa2bc..f2d9318 100644 --- a/src/lib/utils/markup/asciidoctorPostProcessor.ts +++ b/src/lib/utils/markup/asciidoctorPostProcessor.ts @@ -24,9 +24,10 @@ function replaceWikilinks(html: string): string { (_match, target, label) => { const normalized = normalizeDTag(target.trim()); const display = (label || target).trim(); - const url = `./events?d=${normalized}`; + const url = `/events?d=${normalized}`; // Output as a clickable with the [[display]] format and matching link colors - return `${display}`; + // Use onclick to bypass SvelteKit routing and navigate directly + return `${display}`; }, ); } @@ -37,8 +38,9 @@ function replaceWikilinks(html: string): string { function replaceAsciiDocAnchors(html: string): string { return html.replace(/<\/a>/g, (_match, id) => { const normalized = normalizeDTag(id.trim()); - const url = `./events?d=${normalized}`; - return `${id}`; + const url = `/events?d=${normalized}`; + // Use onclick to bypass SvelteKit routing and navigate directly + return `${id}`; }); } diff --git a/src/lib/utils/markup/basicMarkupParser.ts b/src/lib/utils/markup/basicMarkupParser.ts index 953fba5..fd7fd14 100644 --- a/src/lib/utils/markup/basicMarkupParser.ts +++ b/src/lib/utils/markup/basicMarkupParser.ts @@ -160,9 +160,10 @@ function replaceWikilinks(text: string): string { (_match, target, label) => { const normalized = normalizeDTag(target.trim()); const display = (label || target).trim(); - const url = `./events?d=${normalized}`; + const url = `/events?d=${normalized}`; // Output as a clickable with the [[display]] format and matching link colors - return `${display}`; + // Use onclick to bypass SvelteKit routing and navigate directly + return `${display}`; }, ); } diff --git a/src/lib/utils/network_detection.ts b/src/lib/utils/network_detection.ts index 7a0060e..b7a7315 100644 --- a/src/lib/utils/network_detection.ts +++ b/src/lib/utils/network_detection.ts @@ -153,7 +153,7 @@ export function getRelaySetForNetworkCondition( */ export function startNetworkMonitoring( onNetworkChange: (condition: NetworkCondition) => void, - checkInterval: number = 60000 // Increased to 60 seconds to reduce spam + checkInterval: number = 60000, // Increased to 60 seconds to reduce spam ): () => void { let lastCondition: NetworkCondition | null = null; let intervalId: ReturnType | null = null; diff --git a/src/lib/utils/nostrUtils.ts b/src/lib/utils/nostrUtils.ts index 14b04d8..c36108f 100644 --- a/src/lib/utils/nostrUtils.ts +++ b/src/lib/utils/nostrUtils.ts @@ -148,7 +148,7 @@ export function createProfileLink( const escapedText = escapeHtml(displayText || defaultText); // Remove target="_blank" for internal navigation - return `@${escapedText}`; + return `@${escapedText}`; } /** @@ -231,9 +231,9 @@ export async function createProfileLinkWithVerification( const type = nip05.endsWith("edu") ? "edu" : "standard"; switch (type) { case "edu": - return `@${displayIdentifier}${graduationCapSvg}`; + return `@${displayIdentifier}${graduationCapSvg}`; case "standard": - return `@${displayIdentifier}${badgeCheckSvg}`; + return `@${displayIdentifier}${badgeCheckSvg}`; } } /** @@ -245,7 +245,7 @@ function createNoteLink(identifier: string): string { const escapedId = escapeHtml(cleanId); const escapedText = escapeHtml(shortId); - return `${escapedText}`; + return `${escapedText}`; } /** @@ -429,6 +429,9 @@ Promise.prototype.withTimeout = function ( return withTimeout(timeoutMs, this); }; +// TODO: Implement fetch for no-auth relays using the WebSocketPool and raw WebSockets. +// This fetch function will be used for server-side loading. + /** * Fetches an event using a two-step relay strategy: * 1. First tries standard relays with timeout diff --git a/src/lib/utils/nostr_identifiers.ts b/src/lib/utils/nostr_identifiers.ts new file mode 100644 index 0000000..8e789d7 --- /dev/null +++ b/src/lib/utils/nostr_identifiers.ts @@ -0,0 +1,88 @@ +import { VALIDATION } from './search_constants'; + +/** + * Nostr identifier types + */ +export type NostrEventId = string; // 64-character hex string +export type NostrCoordinate = string; // kind:pubkey:d-tag format +export type NostrIdentifier = NostrEventId | NostrCoordinate; + +/** + * Interface for parsed Nostr coordinate + */ +export interface ParsedCoordinate { + kind: number; + pubkey: string; + dTag: string; +} + +/** + * Check if a string is a valid hex event ID + * @param id The string to check + * @returns True if it's a valid hex event ID + */ +export function isEventId(id: string): id is NostrEventId { + return new RegExp(`^[a-f0-9]{${VALIDATION.HEX_LENGTH}}$`, 'i').test(id); +} + +/** + * Check if a string is a valid Nostr coordinate (kind:pubkey:d-tag) + * @param coordinate The string to check + * @returns True if it's a valid coordinate + */ +export function isCoordinate(coordinate: string): coordinate is NostrCoordinate { + const parts = coordinate.split(':'); + if (parts.length < 3) return false; + + const [kindStr, pubkey, ...dTagParts] = parts; + + // Check if kind is a valid number + const kind = parseInt(kindStr, 10); + if (isNaN(kind) || kind < 0) return false; + + // Check if pubkey is a valid hex string + if (!isEventId(pubkey)) return false; + + // Check if d-tag exists (can contain colons) + if (dTagParts.length === 0) return false; + + return true; +} + +/** + * Parse a Nostr coordinate into its components + * @param coordinate The coordinate string to parse + * @returns Parsed coordinate or null if invalid + */ +export function parseCoordinate(coordinate: string): ParsedCoordinate | null { + if (!isCoordinate(coordinate)) return null; + + const parts = coordinate.split(':'); + const [kindStr, pubkey, ...dTagParts] = parts; + + return { + kind: parseInt(kindStr, 10), + pubkey, + dTag: dTagParts.join(':') // Rejoin in case d-tag contains colons + }; +} + +/** + * Create a coordinate string from components + * @param kind The event kind + * @param pubkey The author's public key + * @param dTag The d-tag value + * @returns The coordinate string + */ +export function createCoordinate(kind: number, pubkey: string, dTag: string): NostrCoordinate { + return `${kind}:${pubkey}:${dTag}`; +} + +/** + * Check if a string is any valid Nostr identifier + * @param identifier The string to check + * @returns True if it's a valid Nostr identifier + */ +export function isNostrIdentifier(identifier: string): identifier is NostrIdentifier { + return isEventId(identifier) || isCoordinate(identifier); +} \ No newline at end of file diff --git a/src/lib/utils/profileCache.ts b/src/lib/utils/profileCache.ts new file mode 100644 index 0000000..2a93a45 --- /dev/null +++ b/src/lib/utils/profileCache.ts @@ -0,0 +1,252 @@ +import type { NDKEvent } from "@nostr-dev-kit/ndk"; +import { ndkInstance } from "$lib/ndk"; +import { get } from "svelte/store"; +import { nip19 } from "nostr-tools"; + +interface ProfileData { + display_name?: string; + name?: string; + picture?: string; + about?: string; +} + +// Cache for user profiles +const profileCache = new Map(); + +/** + * Fetches profile data for a pubkey + * @param pubkey - The public key to fetch profile for + * @returns Profile data or null if not found + */ +async function fetchProfile(pubkey: string): Promise { + try { + const ndk = get(ndkInstance); + const profileEvents = await ndk.fetchEvents({ + kinds: [0], + authors: [pubkey], + limit: 1 + }); + + if (profileEvents.size === 0) { + return null; + } + + // Get the most recent profile event + const profileEvent = Array.from(profileEvents)[0]; + + try { + const content = JSON.parse(profileEvent.content); + return content as ProfileData; + } catch (e) { + console.error("Failed to parse profile content:", e); + return null; + } + } catch (e) { + console.error("Failed to fetch profile:", e); + return null; + } +} + +/** + * Gets the display name for a pubkey, using cache + * @param pubkey - The public key to get display name for + * @returns Display name, name, or shortened pubkey + */ +export async function getDisplayName(pubkey: string): Promise { + // Check cache first + if (profileCache.has(pubkey)) { + const profile = profileCache.get(pubkey)!; + return profile.display_name || profile.name || shortenPubkey(pubkey); + } + + // Fetch profile + const profile = await fetchProfile(pubkey); + if (profile) { + profileCache.set(pubkey, profile); + return profile.display_name || profile.name || shortenPubkey(pubkey); + } + + // Fallback to shortened pubkey + return shortenPubkey(pubkey); +} + +/** + * Batch fetches profiles for multiple pubkeys + * @param pubkeys - Array of public keys to fetch profiles for + * @param onProgress - Optional callback for progress updates + * @returns Array of profile events + */ +export async function batchFetchProfiles( + pubkeys: string[], + onProgress?: (fetched: number, total: number) => void +): Promise { + const allProfileEvents: NDKEvent[] = []; + + // Filter out already cached pubkeys + const uncachedPubkeys = pubkeys.filter(pk => !profileCache.has(pk)); + + if (uncachedPubkeys.length === 0) { + if (onProgress) onProgress(pubkeys.length, pubkeys.length); + return allProfileEvents; + } + + try { + const ndk = get(ndkInstance); + + // Report initial progress + const cachedCount = pubkeys.length - uncachedPubkeys.length; + if (onProgress) onProgress(cachedCount, pubkeys.length); + + // Batch fetch in chunks to avoid overwhelming relays + const CHUNK_SIZE = 50; + let fetchedCount = cachedCount; + + for (let i = 0; i < uncachedPubkeys.length; i += CHUNK_SIZE) { + const chunk = uncachedPubkeys.slice(i, Math.min(i + CHUNK_SIZE, uncachedPubkeys.length)); + + const profileEvents = await ndk.fetchEvents({ + kinds: [0], + authors: chunk + }); + + // Process each profile event + profileEvents.forEach((event: NDKEvent) => { + try { + const content = JSON.parse(event.content); + profileCache.set(event.pubkey, content as ProfileData); + allProfileEvents.push(event); + fetchedCount++; + } catch (e) { + console.error("Failed to parse profile content:", e); + } + }); + + // Update progress + if (onProgress) { + onProgress(fetchedCount, pubkeys.length); + } + } + + // Final progress update + if (onProgress) onProgress(pubkeys.length, pubkeys.length); + } catch (e) { + console.error("Failed to batch fetch profiles:", e); + } + + return allProfileEvents; +} + +/** + * Gets display name synchronously from cache + * @param pubkey - The public key to get display name for + * @returns Display name, name, or shortened pubkey + */ +export function getDisplayNameSync(pubkey: string): string { + if (profileCache.has(pubkey)) { + const profile = profileCache.get(pubkey)!; + return profile.display_name || profile.name || shortenPubkey(pubkey); + } + return shortenPubkey(pubkey); +} + +/** + * Shortens a pubkey for display + * @param pubkey - The public key to shorten + * @returns Shortened pubkey (first 8 chars...last 4 chars) + */ +function shortenPubkey(pubkey: string): string { + if (pubkey.length <= 12) return pubkey; + return `${pubkey.slice(0, 8)}...${pubkey.slice(-4)}`; +} + +/** + * Clears the profile cache + */ +export function clearProfileCache(): void { + profileCache.clear(); +} + +/** + * Extracts all pubkeys from events (authors and p tags) + * @param events - Array of events to extract pubkeys from + * @returns Set of unique pubkeys + */ +export function extractPubkeysFromEvents(events: NDKEvent[]): Set { + const pubkeys = new Set(); + + events.forEach(event => { + // Add author pubkey + if (event.pubkey) { + pubkeys.add(event.pubkey); + } + + // Add pubkeys from p tags + const pTags = event.getMatchingTags("p"); + pTags.forEach(tag => { + if (tag[1]) { + pubkeys.add(tag[1]); + } + }); + + // Extract pubkeys from content (nostr:npub1... format) + const npubPattern = /nostr:npub1[a-z0-9]{58}/g; + const matches = event.content?.match(npubPattern) || []; + matches.forEach(match => { + try { + const npub = match.replace('nostr:', ''); + const decoded = nip19.decode(npub); + if (decoded.type === 'npub') { + pubkeys.add(decoded.data as string); + } + } catch (e) { + // Invalid npub, ignore + } + }); + }); + + return pubkeys; +} + +/** + * Replaces pubkeys in content with display names + * @param content - The content to process + * @returns Content with pubkeys replaced by display names + */ +export function replaceContentPubkeys(content: string): string { + if (!content) return content; + + // Replace nostr:npub1... references + const npubPattern = /nostr:npub[a-z0-9]{58}/g; + let result = content; + + const matches = content.match(npubPattern) || []; + matches.forEach(match => { + try { + const npub = match.replace('nostr:', ''); + const decoded = nip19.decode(npub); + if (decoded.type === 'npub') { + const pubkey = decoded.data as string; + const displayName = getDisplayNameSync(pubkey); + result = result.replace(match, `@${displayName}`); + } + } catch (e) { + // Invalid npub, leave as is + } + }); + + return result; +} + +/** + * Replaces pubkey references in text with display names + * @param text - Text that may contain pubkey references + * @returns Text with pubkeys replaced by display names + */ +export function replacePubkeysWithDisplayNames(text: string): string { + // Match hex pubkeys (64 characters) + const pubkeyRegex = /\b[0-9a-fA-F]{64}\b/g; + + return text.replace(pubkeyRegex, (match) => { + return getDisplayNameSync(match); + }); +} \ No newline at end of file diff --git a/src/lib/utils/tag_event_fetch.ts b/src/lib/utils/tag_event_fetch.ts new file mode 100644 index 0000000..077a93e --- /dev/null +++ b/src/lib/utils/tag_event_fetch.ts @@ -0,0 +1,206 @@ +import type { NDKEvent } from "@nostr-dev-kit/ndk"; +import { ndkInstance } from "../ndk"; +import { get } from "svelte/store"; +import { extractPubkeysFromEvents, batchFetchProfiles } from "./profileCache"; + +// Constants for publication event kinds +const INDEX_EVENT_KIND = 30040; +const CONTENT_EVENT_KINDS = [30041, 30818]; + +/** + * Interface for tag expansion fetch results + */ +export interface TagExpansionResult { + publications: NDKEvent[]; + contentEvents: NDKEvent[]; +} + +/** + * Fetches publications and their content events from relays based on tags + * + * This function handles the relay-based fetching portion of tag expansion: + * 1. Fetches publication index events that have any of the specified tags + * 2. Extracts content event references from those publications + * 3. Fetches the referenced content events + * + * @param tags Array of tags to search for in publications + * @param existingEventIds Set of existing event IDs to avoid duplicates + * @param baseEvents Array of base events to check for existing content + * @param debug Optional debug function for logging + * @returns Promise resolving to publications and content events + */ +export async function fetchTaggedEventsFromRelays( + tags: string[], + existingEventIds: Set, + baseEvents: NDKEvent[], + debug?: (...args: any[]) => void +): Promise { + const log = debug || console.debug; + + log("Fetching from relays for tags:", tags); + + // Fetch publications that have any of the specified tags + const ndk = get(ndkInstance); + const taggedPublications = await ndk.fetchEvents({ + kinds: [INDEX_EVENT_KIND], + "#t": tags, // Match any of these tags + limit: 30 // Reasonable default limit + }); + + log("Found tagged publications from relays:", taggedPublications.size); + + // Filter to avoid duplicates + const newPublications = Array.from(taggedPublications).filter( + (event: NDKEvent) => !existingEventIds.has(event.id) + ); + + // Extract content event d-tags from new publications + const contentEventDTags = new Set(); + const existingContentDTags = new Set( + baseEvents + .filter(e => e.kind !== undefined && CONTENT_EVENT_KINDS.includes(e.kind)) + .map(e => e.tagValue("d")) + .filter(d => d !== undefined) + ); + + newPublications.forEach((event: NDKEvent) => { + const aTags = event.getMatchingTags("a"); + aTags.forEach((tag: string[]) => { + // Parse the 'a' tag identifier: kind:pubkey:d-tag + if (tag[1]) { + const parts = tag[1].split(':'); + if (parts.length >= 3) { + const dTag = parts.slice(2).join(':'); // Handle d-tags with colons + if (!existingContentDTags.has(dTag)) { + contentEventDTags.add(dTag); + } + } + } + }); + }); + + // Fetch the content events + let newContentEvents: NDKEvent[] = []; + if (contentEventDTags.size > 0) { + const contentEventsSet = await ndk.fetchEvents({ + kinds: CONTENT_EVENT_KINDS, + "#d": Array.from(contentEventDTags), // Use d-tag filter + }); + newContentEvents = Array.from(contentEventsSet); + } + + return { + publications: newPublications, + contentEvents: newContentEvents + }; +} + +/** + * Searches through already fetched events for publications with specified tags + * + * This function handles the local search portion of tag expansion: + * 1. Searches through existing events for publications with matching tags + * 2. Extracts content event references from those publications + * 3. Finds the referenced content events in existing events + * + * @param allEvents Array of all fetched events to search through + * @param tags Array of tags to search for in publications + * @param existingEventIds Set of existing event IDs to avoid duplicates + * @param baseEvents Array of base events to check for existing content + * @param debug Optional debug function for logging + * @returns Promise resolving to publications and content events + */ +export function findTaggedEventsInFetched( + allEvents: NDKEvent[], + tags: string[], + existingEventIds: Set, + baseEvents: NDKEvent[], + debug?: (...args: any[]) => void +): TagExpansionResult { + const log = debug || console.debug; + + log("Searching through already fetched events for tags:", tags); + + // Find publications in allEvents that have the specified tags + const taggedPublications = allEvents.filter(event => { + if (event.kind !== INDEX_EVENT_KIND) return false; + if (existingEventIds.has(event.id)) return false; // Skip base events + + // Check if event has any of the specified tags + const eventTags = event.getMatchingTags("t").map(tag => tag[1]); + return tags.some(tag => eventTags.includes(tag)); + }); + + const newPublications = taggedPublications; + log("Found", newPublications.length, "publications in fetched events"); + + // For content events, also search in allEvents + const existingContentDTags = new Set( + baseEvents + .filter(e => e.kind !== undefined && CONTENT_EVENT_KINDS.includes(e.kind)) + .map(e => e.tagValue("d")) + .filter(d => d !== undefined) + ); + + const contentEventDTags = new Set(); + newPublications.forEach((event: NDKEvent) => { + const aTags = event.getMatchingTags("a"); + aTags.forEach((tag: string[]) => { + // Parse the 'a' tag identifier: kind:pubkey:d-tag + if (tag[1]) { + const parts = tag[1].split(':'); + if (parts.length >= 3) { + const dTag = parts.slice(2).join(':'); // Handle d-tags with colons + if (!existingContentDTags.has(dTag)) { + contentEventDTags.add(dTag); + } + } + } + }); + }); + + // Find content events in allEvents + const newContentEvents = allEvents.filter(event => { + if (!CONTENT_EVENT_KINDS.includes(event.kind || 0)) return false; + const dTag = event.tagValue("d"); + return dTag !== undefined && contentEventDTags.has(dTag); + }); + + return { + publications: newPublications, + contentEvents: newContentEvents + }; +} + +/** + * Fetches profiles for new events and updates progress + * + * @param newPublications Array of new publication events + * @param newContentEvents Array of new content events + * @param onProgressUpdate Callback to update progress state + * @param debug Optional debug function for logging + * @returns Promise that resolves when profile fetching is complete + */ +export async function fetchProfilesForNewEvents( + newPublications: NDKEvent[], + newContentEvents: NDKEvent[], + onProgressUpdate: (progress: { current: number; total: number } | null) => void, + debug?: (...args: any[]) => void +): Promise { + const log = debug || console.debug; + + // Extract pubkeys from new events + const newPubkeys = extractPubkeysFromEvents([...newPublications, ...newContentEvents]); + + if (newPubkeys.size > 0) { + log("Fetching profiles for", newPubkeys.size, "new pubkeys from tag expansion"); + + onProgressUpdate({ current: 0, total: newPubkeys.size }); + + await batchFetchProfiles(Array.from(newPubkeys), (fetched, total) => { + onProgressUpdate({ current: fetched, total }); + }); + + onProgressUpdate(null); + } +} \ No newline at end of file diff --git a/src/lib/utils/websocket_utils.ts b/src/lib/utils/websocket_utils.ts new file mode 100644 index 0000000..834bca3 --- /dev/null +++ b/src/lib/utils/websocket_utils.ts @@ -0,0 +1,238 @@ +import { WebSocketPool } from "../data_structures/websocket_pool.ts"; +import { error } from "@sveltejs/kit"; +import { naddrDecode, neventDecode } from "../utils.ts"; +import { activeInboxRelays, activeOutboxRelays } from "../ndk.ts"; +import { get } from "svelte/store"; + +export interface NostrEvent { + id: string; + pubkey: string; + created_at: number; + kind: number; + tags: string[][]; + content: string; + sig: string; +} + +export interface NostrFilter { + ids?: string[]; + authors?: string[]; + kinds?: number[]; + [tag: `#${string}`]: string[] | undefined; + since?: number; + until?: number; + limit?: number; +} + +type ResolveCallback = (value: T | PromiseLike) => void; +type RejectCallback = (reason?: any) => void; +type EventHandler = (ev: Event) => void; +type MessageEventHandler = (ev: MessageEvent) => void; +type EventHandlerReject = (reject: RejectCallback) => EventHandler; +type EventHandlerResolve = (resolve: ResolveCallback) => (reject: RejectCallback) => MessageEventHandler; + +function handleMessage( + ev: MessageEvent, + subId: string, + resolve: (event: NostrEvent) => void, + reject: (reason: any) => void +) { + const data = JSON.parse(ev.data); + + if (data[1] !== subId) { + return; + } + + switch (data[0]) { + case "EVENT": + break; + case "CLOSED": + reject(new Error(`[WebSocket Utils]: Subscription ${subId} closed`)); + break; + case "EOSE": + reject(new Error(`[WebSocket Utils]: Event not found`)); + break; + } + + const event = data[2] as NostrEvent; + if (!event) { + return; + } + + resolve(event); +} + +function handleError( + ev: Event, + reject: (reason: any) => void +) { + reject(ev); +} + +export async function fetchNostrEvent(filter: NostrFilter): Promise { + // AI-NOTE: Updated to use active relay stores instead of hardcoded relay URL + // This ensures the function uses the user's configured relays and can find events + // across multiple relays rather than being limited to a single hardcoded relay. + + // Get available relays from the active relay stores + const inboxRelays = get(activeInboxRelays); + const outboxRelays = get(activeOutboxRelays); + + // Combine all available relays, prioritizing inbox relays + let availableRelays = [...inboxRelays, ...outboxRelays]; + + // AI-NOTE: Use fallback relays when stores are empty (e.g., during SSR) + // This ensures publications can still load even when relay stores haven't been populated + if (availableRelays.length === 0) { + // Import fallback relays from constants + const { searchRelays, secondaryRelays } = await import("../consts.ts"); + availableRelays = [...searchRelays, ...secondaryRelays]; + + if (availableRelays.length === 0) { + availableRelays = ["wss://thecitadel.nostr1.com"]; + } + } + + // Try all available relays in parallel and return the first result + const relayPromises = availableRelays.map(async (relay) => { + try { + const ws = await WebSocketPool.instance.acquire(relay); + const subId = crypto.randomUUID(); + + // AI-NOTE: Currying is used here to abstract the internal handler logic away from the WebSocket + // handling logic. The message and error handlers themselves can be refactored without affecting + // the WebSocket handling logic. + const curriedMessageHandler: (subId: string) => (resolve: ResolveCallback) => (reject: RejectCallback) => MessageEventHandler = + (subId) => + (resolve) => + (reject) => + (ev: MessageEvent) => + handleMessage(ev, subId, resolve, reject); + const curriedErrorHandler: EventHandlerReject = + (reject) => + (ev: Event) => + handleError(ev, reject); + + // AI-NOTE: These variables store references to partially-applied handlers so that the `finally` + // block receives the correct references to clean up the listeners. + let messageHandler: MessageEventHandler; + let errorHandler: EventHandler; + + const res = new Promise((resolve, reject) => { + messageHandler = curriedMessageHandler(subId)(resolve)(reject); + errorHandler = curriedErrorHandler(reject); + + ws.addEventListener("message", messageHandler); + ws.addEventListener("error", errorHandler); + }) + .withTimeout(2000) + .finally(() => { + ws.removeEventListener("message", messageHandler); + ws.removeEventListener("error", errorHandler); + WebSocketPool.instance.release(ws); + }); + + ws.send(JSON.stringify(["REQ", subId, filter])); + + const result = await res; + if (result) { + return result; + } + + return null; + } catch (err) { + return null; + } + }); + + // Wait for all relay results and find the first successful one + const results = await Promise.allSettled(relayPromises); + + // Find the first successful result + for (const result of results) { + if (result.status === 'fulfilled' && result.value) { + return result.value; + } + } + + return null; +} + +/** + * Fetches an event by hex ID, throwing a SvelteKit 404 error if not found. + */ +export async function fetchEventById(id: string): Promise { + try { + const event = await fetchNostrEvent({ ids: [id], limit: 1 }); + if (!event) { + error(404, `Event not found for ID: ${id}. href="/events?id=${id}"`); + } + return event; + } catch (err) { + if (err && typeof err === "object" && "status" in err) { + throw err; + } + error(404, `Failed to fetch event by ID: ${err}`); + } +} + +/** + * Fetches an event by d tag, throwing a 404 if not found. + */ +export async function fetchEventByDTag(dTag: string): Promise { + try { + const event = await fetchNostrEvent({ "#d": [dTag], limit: 1 }); + if (!event) { + error(404, `Event not found for d-tag: ${dTag}. href="/events?d=${dTag}"`); + } + return event; + } catch (err) { + if (err && typeof err === "object" && "status" in err) { + throw err; + } + error(404, `Failed to fetch event by d-tag: ${err}`); + } +} + +/** + * Fetches an event by naddr identifier. + */ +export async function fetchEventByNaddr(naddr: string): Promise { + try { + const decoded = naddrDecode(naddr); + const filter = { + kinds: [decoded.kind], + authors: [decoded.pubkey], + "#d": [decoded.identifier], + }; + const event = await fetchNostrEvent(filter); + if (!event) { + error(404, `Event not found for naddr: ${naddr}. href="/events?id=${naddr}"`); + } + return event; + } catch (err) { + if (err && typeof err === "object" && "status" in err) { + throw err; + } + error(404, `Failed to fetch event by naddr: ${err}`); + } +} + +/** + * Fetches an event by nevent identifier. + */ +export async function fetchEventByNevent(nevent: string): Promise { + try { + const decoded = neventDecode(nevent); + const event = await fetchNostrEvent({ ids: [decoded.id], limit: 1 }); + if (!event) { + error(404, `Event not found for nevent: ${nevent}. href="/events?id=${nevent}"`); + } + return event; + } catch (err) { + if (err && typeof err === "object" && "status" in err) { + throw err; + } + error(404, `Failed to fetch event by nevent: ${err}`); + } +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 47be24c..2fff8a9 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -45,7 +45,7 @@ -
    +
    diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 80555ad..ac50221 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -8,14 +8,16 @@ import { loginMethodStorageKey } from "../lib/stores/userStore.ts"; import Pharos, { pharosInstance } from "../lib/parser.ts"; import type { LayoutLoad } from "./$types"; import { get } from "svelte/store"; +import { browser } from "$app/environment"; +// AI-NOTE: Leave SSR off until event fetches are implemented server-side. export const ssr = false; -export const load: LayoutLoad = () => { - // Initialize NDK with new relay management system - const ndk = initNdk(); - ndkInstance.set(ndk); - +/** + * Attempts to restore the user's authentication session from localStorage. + * Handles extension, Amber (NIP-46), and npub login methods. + */ +function restoreAuthSession() { try { const pubkey = getPersistedLogin(); const loginMethod = localStorage.getItem(loginMethodStorageKey); @@ -113,9 +115,19 @@ export const load: LayoutLoad = () => { `Failed to restore login: ${e}\n\nContinuing with anonymous session.`, ); } +} + +export const load: LayoutLoad = () => { + // Initialize NDK with new relay management system + const ndk = initNdk(); + ndkInstance.set(ndk); + + if (browser) { + restoreAuthSession(); + } const parser = new Pharos(ndk); - pharosInstance.set(parser); + pharosInstance.set(parser); return { ndk, diff --git a/src/routes/about/+page.svelte b/src/routes/about/+page.svelte index 72b4697..715caf7 100644 --- a/src/routes/about/+page.svelte +++ b/src/routes/about/+page.svelte @@ -26,11 +26,11 @@

    Alexandria is a reader and writer for curated publications (in Asciidoc), wiki pages (Asciidoc), and will eventually also support long-form articles (markup). It is produced by the GitCitadel project team.

    diff --git a/src/routes/events/+page.svelte b/src/routes/events/+page.svelte index ae93f34..15c469c 100644 --- a/src/routes/events/+page.svelte +++ b/src/routes/events/+page.svelte @@ -354,9 +354,9 @@
    -
    +
    -
    +
    Events @@ -775,7 +775,7 @@ {#if showSidePanel && event} -
    +
    Event Details + {/each} +
    +
    +

    Tag Filter

    + {#if tagsToShow.length > 0} + + {/if} +
    +
    + {#each tagsToShow as tag} + + {/each} +
    + + + +
    +

    My Notes

    + {#if loading} +
    Loading…
    + {:else if error} +
    {error}
    + {:else if filteredEvents.length === 0} +
    No notes found.
    + {:else} +
      + {#each filteredEvents as event} +
    • +
      +
      {getTitle(event)}
      + +
      + {#if showTags[event.id]} +
      + {#each getTags(event) as tag} + + {tag[0]}: + {tag[1]} + + {/each} +
      + {/if} +
      + {event.created_at + ? new Date(event.created_at * 1000).toLocaleString() + : ""} +
      +
      + {@html renderedContent[event.id] || ""} +
      +
    • + {/each} +
    + {/if} +
    +
    diff --git a/src/routes/proxy+layout.ts b/src/routes/proxy+layout.ts new file mode 100644 index 0000000..8a97a72 --- /dev/null +++ b/src/routes/proxy+layout.ts @@ -0,0 +1,5 @@ +import type { LayoutLoad } from "./$types"; + +export const load: LayoutLoad = async () => { + return {}; +}; \ No newline at end of file diff --git a/src/routes/publication/+error.svelte b/src/routes/publication/+error.svelte index 9d0d347..c9d1ce2 100644 --- a/src/routes/publication/+error.svelte +++ b/src/routes/publication/+error.svelte @@ -3,28 +3,125 @@ import { Alert, P, Button } from "flowbite-svelte"; import { ExclamationCircleOutline } from "flowbite-svelte-icons"; import { page } from "$app/state"; + + // Parse error message to extract search parameters and format it nicely + function parseErrorMessage(message: string): { + errorType: string; + identifier: string; + searchUrl?: string; + shortIdentifier?: string; + } { + const searchLinkMatch = message.match(/href="([^"]+)"/); + let searchUrl: string | undefined; + let baseMessage = message; + + if (searchLinkMatch) { + searchUrl = searchLinkMatch[1]; + baseMessage = message.replace(/href="[^"]+"/, '').trim(); + } + + // Extract error type and identifier from the message + const match = baseMessage.match(/Event not found for (\w+): (.+)/); + if (match) { + const errorType = match[1]; + const fullIdentifier = match[2]; + const shortIdentifier = fullIdentifier.length > 50 + ? fullIdentifier.substring(0, 47) + '...' + : fullIdentifier; + + return { + errorType, + identifier: fullIdentifier, + searchUrl, + shortIdentifier + }; + } + + return { + errorType: 'unknown', + identifier: baseMessage, + searchUrl, + shortIdentifier: baseMessage.length > 50 + ? baseMessage.substring(0, 47) + '...' + : baseMessage + }; + } + + $: errorInfo = page.error?.message ? parseErrorMessage(page.error.message) : { + errorType: 'unknown', + identifier: '', + shortIdentifier: '' + }; -
    - -
    - - Failed to load publication. +
    + +
    + + + Failed to load publication +
    -

    - Alexandria failed to find one or more of the events comprising this - publication. -

    -

    - {page.error?.message} + +

    + Alexandria failed to find one or more of the events comprising this publication.

    + +
    +
    + + Error Type: + + + {errorInfo.errorType} + +
    + +
    + + Identifier: + +
    +
    + {errorInfo.shortIdentifier} +
    + {#if errorInfo.identifier.length > 50} +
    + + Show full identifier + +
    + {errorInfo.identifier} +
    +
    + {/if} +
    +
    +
    + + {#if errorInfo.searchUrl} +
    + +
    + {/if} +
    diff --git a/src/routes/publication/+page.server.ts b/src/routes/publication/+page.server.ts new file mode 100644 index 0000000..fa30a0d --- /dev/null +++ b/src/routes/publication/+page.server.ts @@ -0,0 +1,41 @@ +import { redirect } from "@sveltejs/kit"; +import type { PageServerLoad } from "./$types"; + +// Route pattern constants +const ROUTES = { + PUBLICATION_BASE: "/publication", + NADDR: "/publication/naddr", + NEVENT: "/publication/nevent", + ID: "/publication/id", + D_TAG: "/publication/d", + START: "/start", +} as const; + +// Identifier prefixes +const IDENTIFIER_PREFIXES = { + NADDR: "naddr", + NEVENT: "nevent", +} as const; + +export const load: PageServerLoad = ({ url }) => { + const id = url.searchParams.get("id"); + const dTag = url.searchParams.get("d"); + + // Handle backward compatibility for old query-based routes + if (id) { + // Check if id is an naddr or nevent + if (id.startsWith(IDENTIFIER_PREFIXES.NADDR)) { + redirect(301, `${ROUTES.NADDR}/${id}`); + } else if (id.startsWith(IDENTIFIER_PREFIXES.NEVENT)) { + redirect(301, `${ROUTES.NEVENT}/${id}`); + } else { + // Assume it's a hex ID + redirect(301, `${ROUTES.ID}/${id}`); + } + } else if (dTag) { + redirect(301, `${ROUTES.D_TAG}/${dTag}`); + } + + // If no query parameters, redirect to the start page + redirect(301, ROUTES.START); +}; \ No newline at end of file diff --git a/src/routes/publication/+page.svelte b/src/routes/publication/+page.svelte deleted file mode 100644 index eacc71b..0000000 --- a/src/routes/publication/+page.svelte +++ /dev/null @@ -1,134 +0,0 @@ - - - - - {title} - - - - - - - - - - - - - - - - - - - -
    - -
    diff --git a/src/routes/publication/+page.ts b/src/routes/publication/+page.ts deleted file mode 100644 index a79423f..0000000 --- a/src/routes/publication/+page.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { error } from "@sveltejs/kit"; -import type { Load } from "@sveltejs/kit"; -import type { NDKEvent } from "@nostr-dev-kit/ndk"; -import { nip19 } from "nostr-tools"; -import { getActiveRelaySetAsNDKRelaySet } from "../../lib/ndk.ts"; -import { getMatchingTags } from "../../lib/utils/nostrUtils.ts"; -import type NDK from "@nostr-dev-kit/ndk"; - -/** - * Decodes an naddr identifier and returns a filter object - */ -function decodeNaddr(id: string) { - try { - if (!id.startsWith("naddr")) return {}; - - const decoded = nip19.decode(id); - if (decoded.type !== "naddr") return {}; - - const data = decoded.data; - return { - kinds: [data.kind], - authors: [data.pubkey], - "#d": [data.identifier], - }; - } catch (e) { - console.error("Failed to decode naddr:", e); - return null; - } -} - -/** - * Fetches an event by ID or filter - */ -async function fetchEventById(ndk: NDK, id: string): Promise { - const filter = decodeNaddr(id); - - // Handle the case where filter is null (decoding error) - if (filter === null) { - // If we can't decode the naddr, try using the raw ID - try { - const event = await ndk.fetchEvent(id); - if (!event) { - throw new Error(`Event not found for ID: ${id}`); - } - return event; - } catch (err) { - throw error(404, `Failed to fetch publication root event.\n${err}`); - } - } - - const hasFilter = Object.keys(filter).length > 0; - - try { - const event = await (hasFilter - ? ndk.fetchEvent(filter) - : ndk.fetchEvent(id)); - - if (!event) { - throw new Error(`Event not found for ID: ${id}`); - } - return event; - } catch (err) { - throw error(404, `Failed to fetch publication root event.\n${err}`); - } -} - -/** - * Fetches an event by d tag - */ -async function fetchEventByDTag(ndk: NDK, dTag: string): Promise { - try { - const relaySet = await getActiveRelaySetAsNDKRelaySet(ndk, true); // true for inbox relays - const event = await ndk.fetchEvent( - { "#d": [dTag] }, - { closeOnEose: false }, - relaySet, - ); - - if (!event) { - throw new Error(`Event not found for d tag: ${dTag}`); - } - return event; - } catch (err) { - throw error(404, `Failed to fetch publication root event.\n${err}`); - } -} - -// TODO: Use path params instead of query params. -export const load: Load = async ({ - url, - parent, -}: { - url: URL; - parent: () => Promise>>; -}) => { - const id = url.searchParams.get("id"); - const dTag = url.searchParams.get("d"); - const { ndk } = await parent(); - - if (!id && !dTag) { - throw error(400, "No publication root event ID or d tag provided."); - } - - // Fetch the event based on available parameters - const indexEvent = id - ? await fetchEventById(ndk!, id) - : await fetchEventByDTag(ndk!, dTag!); - - const publicationType = getMatchingTags(indexEvent, "type")[0]?.[1]; - - return { - publicationType, - indexEvent, - }; -}; diff --git a/src/routes/publication/[type]/[identifier]/+layout.server.ts b/src/routes/publication/[type]/[identifier]/+layout.server.ts new file mode 100644 index 0000000..2a90624 --- /dev/null +++ b/src/routes/publication/[type]/[identifier]/+layout.server.ts @@ -0,0 +1,34 @@ +import { error } from "@sveltejs/kit"; +import type { LayoutServerLoad } from "./$types"; +import type { NostrEvent } from "../../../../lib/utils/websocket_utils.ts"; + +// AI-NOTE: Server-side event fetching for SEO metadata +async function fetchEventServerSide(type: string, identifier: string): Promise { + // For now, return null to indicate server-side fetch not implemented + // This will fall back to client-side fetching + return null; +} + +export const load: LayoutServerLoad = async ({ params, url }) => { + const { type, identifier } = params; + + // Try to fetch event server-side for metadata + const indexEvent = await fetchEventServerSide(type, identifier); + + // Extract metadata for meta tags (use fallbacks if no event found) + const title = indexEvent?.tags.find((tag) => tag[0] === "title")?.[1] || "Alexandria Publication"; + const summary = indexEvent?.tags.find((tag) => tag[0] === "summary")?.[1] || + "Alexandria is a digital library, utilizing Nostr events for curated publications and wiki pages."; + const image = indexEvent?.tags.find((tag) => tag[0] === "image")?.[1] || "/screenshots/old_books.jpg"; + const currentUrl = `${url.origin}${url.pathname}`; + + return { + indexEvent, // Will be null, triggering client-side fetch + metadata: { + title, + summary, + image, + currentUrl, + }, + }; +}; \ No newline at end of file diff --git a/src/routes/publication/[type]/[identifier]/+layout.svelte b/src/routes/publication/[type]/[identifier]/+layout.svelte new file mode 100644 index 0000000..c14d288 --- /dev/null +++ b/src/routes/publication/[type]/[identifier]/+layout.svelte @@ -0,0 +1,34 @@ + + + + + + {metadata.title} + + + + + + + + + + + + + + + + + +{#if browser} + {@render children()} +{/if} diff --git a/src/routes/publication/[type]/[identifier]/+layout.ts b/src/routes/publication/[type]/[identifier]/+layout.ts new file mode 100644 index 0000000..77ab0a0 --- /dev/null +++ b/src/routes/publication/[type]/[identifier]/+layout.ts @@ -0,0 +1 @@ +export const ssr = true; diff --git a/src/routes/publication/[type]/[identifier]/+page.svelte b/src/routes/publication/[type]/[identifier]/+page.svelte new file mode 100644 index 0000000..fb1cf56 --- /dev/null +++ b/src/routes/publication/[type]/[identifier]/+page.svelte @@ -0,0 +1,126 @@ + + +{#if indexEvent && data.indexEvent} + {@const debugInfo = `indexEvent: ${!!indexEvent}, data.indexEvent: ${!!data.indexEvent}`} + {@const debugElement = console.debug('[Publication] Rendering publication with:', debugInfo)} + + +
    + +
    +{:else} + {@const debugInfo = `indexEvent: ${!!indexEvent}, data.indexEvent: ${!!data.indexEvent}`} + {@const debugElement = console.debug('[Publication] NOT rendering publication with:', debugInfo)} +
    +
    +

    Loading publication...

    +
    +
    +{/if} \ No newline at end of file diff --git a/src/routes/publication/[type]/[identifier]/+page.ts b/src/routes/publication/[type]/[identifier]/+page.ts new file mode 100644 index 0000000..8f3bbaf --- /dev/null +++ b/src/routes/publication/[type]/[identifier]/+page.ts @@ -0,0 +1,80 @@ +import { error } from "@sveltejs/kit"; +import type { PageLoad } from "./$types"; +import { fetchEventByDTag, fetchEventById, fetchEventByNaddr, fetchEventByNevent } from "../../../../lib/utils/websocket_utils.ts"; +import type { NostrEvent } from "../../../../lib/utils/websocket_utils.ts"; + +export const load: PageLoad = async ({ params, parent }: { params: { type: string; identifier: string }; parent: any }) => { + const { type, identifier } = params; + + // Get layout data (no server-side data since SSR is disabled) + const layoutData = await parent(); + + // AI-NOTE: Always fetch client-side since server-side fetch returns null for now + let indexEvent: NostrEvent | null = null; + + try { + // Handle different identifier types + switch (type) { + case 'id': + indexEvent = await fetchEventById(identifier); + break; + case 'd': + indexEvent = await fetchEventByDTag(identifier); + break; + case 'naddr': + indexEvent = await fetchEventByNaddr(identifier); + break; + case 'nevent': + indexEvent = await fetchEventByNevent(identifier); + break; + default: + error(400, `Unsupported identifier type: ${type}`); + } + } catch (err) { + throw err; + } + + if (!indexEvent) { + // AI-NOTE: Handle case where no relays are available during preloading + // This prevents 404 errors when relay stores haven't been populated yet + + // Create appropriate search link based on type + let searchParam = ''; + switch (type) { + case 'id': + searchParam = `id=${identifier}`; + break; + case 'd': + searchParam = `d=${identifier}`; + break; + case 'naddr': + case 'nevent': + searchParam = `id=${identifier}`; + break; + default: + searchParam = `q=${identifier}`; + } + + error(404, `Event not found for ${type}: ${identifier}. href="/events?${searchParam}"`); + } + + const publicationType = indexEvent.tags.find((tag) => tag[0] === "type")?.[1] ?? ""; + + // AI-NOTE: Use proper NDK instance from layout or create one with relays + let ndk = layoutData?.ndk; + if (!ndk) { + // Import NDK dynamically to avoid SSR issues + const NDK = (await import("@nostr-dev-kit/ndk")).default; + // Import initNdk to get properly configured NDK with relays + const { initNdk } = await import("$lib/ndk"); + ndk = initNdk(); + } + + const result = { + publicationType, + indexEvent, + ndk, // Use minimal NDK instance + }; + + return result; +}; diff --git a/src/routes/start/+page.svelte b/src/routes/start/+page.svelte index ff617c6..6cb37a3 100644 --- a/src/routes/start/+page.svelte +++ b/src/routes/start/+page.svelte @@ -91,7 +91,7 @@

    An example of a book is Jane Eyre

    @@ -127,7 +127,7 @@

    An example of a research paper is Less Partnering, Less Children, or Both?

    @@ -145,9 +145,9 @@

    Our own team uses Alexandria to document the app, to display our blog entriesblog entries, as well as to store copies of our most interesting - technical specifications.

    @@ -168,7 +168,7 @@ collaborative knowledge bases and documentation. Wiki pages, such as this one about the goto("/publication/d/sybil")}>Sybil utility use the same Asciidoc format as other publications but are specifically designed for interconnected, evolving content.

    diff --git a/src/routes/visualize/+page.svelte b/src/routes/visualize/+page.svelte index 66e4516..91925ec 100644 --- a/src/routes/visualize/+page.svelte +++ b/src/routes/visualize/+page.svelte @@ -6,17 +6,37 @@ -->